fastapi-streams 0.3.1__tar.gz → 0.4.0__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.
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/CHANGELOG.md +32 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/PKG-INFO +1 -1
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/docs/clients.md +59 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/docs/codegen.md +29 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/errors.py +3 -3
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/errors.ts +9 -2
- fastapi_streams-0.4.0/examples/agent_runs/generated/.fastapi-streams.json +19 -0
- fastapi_streams-0.4.0/examples/agent_runs/generated_python/.fastapi-streams.json +19 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/__init__.py +1 -1
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/cli.py +28 -23
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/writer.py +9 -2
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/pyproject.toml +1 -1
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/scripts/smoke_package.py +5 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_codegen.py +38 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_nested_models.py +20 -10
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_python_client.py +14 -4
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_structured_diagnostics.py +11 -1
- fastapi_streams-0.4.0/tests/fixtures/generated/.fastapi-streams.json +19 -0
- fastapi_streams-0.4.0/tests/fixtures/generated_python/.fastapi-streams.json +19 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/typescript/errors.test.ts +16 -1
- fastapi_streams-0.3.1/examples/agent_runs/generated/.fastapi-streams.json +0 -11
- fastapi_streams-0.3.1/examples/agent_runs/generated_python/.fastapi-streams.json +0 -11
- fastapi_streams-0.3.1/tests/fixtures/generated/.fastapi-streams.json +0 -11
- fastapi_streams-0.3.1/tests/fixtures/generated_python/.fastapi-streams.json +0 -11
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/.gitattributes +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/.github/workflows/ci.yml +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/.github/workflows/compatibility.yml +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/.github/workflows/release.yml +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/.gitignore +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/.pre-commit-config.yaml +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/AGENTS.md +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/LICENSE +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/README.md +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/docs/README.md +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/docs/architecture.md +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/docs/development.md +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/docs/native-transports.md +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/docs/quickstart.md +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/docs/releasing.md +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/__init__.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/__init__.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/consume.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/consume.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated/agent-runs/api.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated/agent-runs/index.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated/client.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated/index.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated/transport/errors.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated/transport/sse.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated/types.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated_python/__init__.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated_python/agent_runs/__init__.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated_python/agent_runs/api.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated_python/client.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated_python/models.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated_python/py.typed +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated_python/runtime.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/native_proxy.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/rest_codegen.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/__init__.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/diagnostics.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/documentation.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/enums.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/ir.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/names.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/openapi.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/python.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/schemas/LICENSE +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/schemas/README.md +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/schemas/openapi-3.2.json +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/templates/errors.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/templates/python/api.py.j2 +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/templates/python/client.py.j2 +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/templates/python/runtime.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/templates/sse.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/templates/typescript/api.ts.j2 +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/templates/typescript/client.ts.j2 +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/templating.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/typescript.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/py.typed +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/router.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/package-lock.json +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/package.json +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/scripts/benchmark_runtime.mjs +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/scripts/benchmark_runtime.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/scripts/check_package.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/scripts/check_release.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/scripts/codegen.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/scripts/test_compat.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/__init__.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/helpers.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_additional_properties.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_contract_boundaries.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_diagnostics.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_discriminators.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_documentation.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_error_contracts.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_field_names.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_http_backends.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_intersections.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_literal_fields.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_native_contract.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_python_typing.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_request_validation.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_required_properties.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_scalar_constraints.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_schema_matrix.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_stream_validation_details.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_unknown_events.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/contracts/test_openapi.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/README.md +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/__init__.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/agent_runs.openapi.json +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/agent_runs.typescript.diagnostics.json +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/compat-client.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated/client.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated/fixtures/api.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated/fixtures/index.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated/index.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated/transport/errors.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated/transport/sse.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated/types.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated_python/__init__.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated_python/client.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated_python/fixtures/__init__.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated_python/fixtures/api.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated_python/models.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated_python/py.typed +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated_python/runtime.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/integration_app.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/models.openapi.json +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/models_app.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/integration/test_end_to_end.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/router/test_router.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/test_release.py +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/typescript/live-client.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/typescript/models.test.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/typescript/narrowing.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/typescript/sse.test.ts +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/typescript/tsconfig.json +0 -0
- {fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/typing/client_usage.py +0 -0
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
All notable changes and consumer upgrade steps are recorded here. Entries marked
|
|
4
4
|
unreleased describe prepared source versions that have not been published.
|
|
5
5
|
|
|
6
|
+
## 0.4.0 - 2026-10-18
|
|
7
|
+
|
|
8
|
+
### Breaking changes
|
|
9
|
+
|
|
10
|
+
- CLI drift checks now include SDK provenance in `.fastapi-streams.json`.
|
|
11
|
+
Existing files-only manifests, a different generator version, or a changed
|
|
12
|
+
source schema now fail `check`, including source changes that leave generated
|
|
13
|
+
code identical. Regenerate once with the existing language and HTTP backend,
|
|
14
|
+
review and commit the updated manifest. This stricter drift check requires
|
|
15
|
+
a minor release under the pre-1.0 version policy.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Always record generator/version, canonical schema SHA-256, target and Python
|
|
20
|
+
HTTP backend in CLI-generated manifests, sharing metadata with diagnostic
|
|
21
|
+
reports. Preserve `files` ownership, safe cleanup and read-only checks.
|
|
22
|
+
Low-level file writers can supply optional `generation` metadata.
|
|
23
|
+
- Document optional operation/status-specific error decoders using the existing
|
|
24
|
+
TypeScript guard hook and cached Python TypeAdapters. Add a checked consumer
|
|
25
|
+
decoder example; unexpected JSON/text/proxy errors retain the generic fallback.
|
|
26
|
+
Generated request/event APIs, HTTP/SSE contracts and runtime dependencies stay
|
|
27
|
+
unchanged.
|
|
28
|
+
|
|
29
|
+
### Upgrade from 0.3.1
|
|
30
|
+
|
|
31
|
+
1. Upgrade the generator to 0.4.0 and regenerate with the same language and
|
|
32
|
+
Python HTTP backend. Commit `.fastapi-streams.json` with the generated SDK.
|
|
33
|
+
2. Expect a one-time manifest diff. CI checks now detect schema or generator
|
|
34
|
+
provenance changes even when generated source code is otherwise unchanged.
|
|
35
|
+
3. Existing callers, HTTP/SSE contracts and exact diagnostic allowances require
|
|
36
|
+
no migration. Optional error decoders remain consumer-controlled.
|
|
37
|
+
|
|
6
38
|
## 0.3.1 - 2026-10-18
|
|
7
39
|
|
|
8
40
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: fastapi-streams
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Typed FastAPI streams with generated TypeScript and Python clients.
|
|
5
5
|
Project-URL: Documentation, https://github.com/mathisarends/canon/tree/main/packages/fastapi-streams/docs
|
|
6
6
|
Project-URL: Source, https://github.com/mathisarends/canon/tree/main/packages/fastapi-streams
|
|
@@ -195,6 +195,65 @@ the original status, response, and diagnostics. Schema unions with permissive
|
|
|
195
195
|
variants may accept a broad range of bodies; inspect the parsed variant before
|
|
196
196
|
using its fields. Existing limits on bounded HTTP diagnostics still apply.
|
|
197
197
|
|
|
198
|
+
### Optional consumer decoders
|
|
199
|
+
|
|
200
|
+
Build a decoder once per operation/status around the existing guard hook when
|
|
201
|
+
several callers need the same declared error body. For example, this adapter
|
|
202
|
+
accepts a consumer-provided validator with no additional SDK dependency:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import { isHttpError, type StreamRunEventsErrorBody } from "./generated/index.js";
|
|
206
|
+
|
|
207
|
+
function decodeValidationError(
|
|
208
|
+
error: unknown,
|
|
209
|
+
isBody: (value: unknown) => value is StreamRunEventsErrorBody,
|
|
210
|
+
): StreamRunEventsErrorBody | undefined {
|
|
211
|
+
if (isHttpError(error, isBody) && error.status === 422) return error.details;
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Supply a handwritten guard or an existing validator that checks every field of
|
|
217
|
+
the generated alias. A type assertion alone does not validate a response. Guards
|
|
218
|
+
must validate the original value: if a decoder coerces or transforms data, use
|
|
219
|
+
its successfully parsed output instead of narrowing the original `error.details`.
|
|
220
|
+
Use this helper only for the operation whose errors the alias describes.
|
|
221
|
+
|
|
222
|
+
If decoding returns `undefined`, continue handling the original `HttpError`
|
|
223
|
+
generically or rethrow it. Its status, response and JSON/text details remain
|
|
224
|
+
available, including unexpected proxy responses. Validators run only for SDK
|
|
225
|
+
HTTP errors; `isHttpError` leaves network/stream failures alone. Status checks
|
|
226
|
+
remain explicit because a matching body does not establish an HTTP status.
|
|
227
|
+
The [TypeScript example](../examples/agent_runs/errors.ts) includes a complete
|
|
228
|
+
guard and reusable decoder with this fallback behavior.
|
|
229
|
+
|
|
230
|
+
In Python, construct and reuse an adapter rather than rebuilding it for each
|
|
231
|
+
exception:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from pydantic import TypeAdapter, ValidationError
|
|
235
|
+
from myapp.generated import HttpError
|
|
236
|
+
from myapp.generated.models import StreamRunEventsErrorBody
|
|
237
|
+
|
|
238
|
+
error_body = TypeAdapter(StreamRunEventsErrorBody)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def decode_validation_error(error: HttpError) -> StreamRunEventsErrorBody | None:
|
|
242
|
+
if error.status != 422:
|
|
243
|
+
return None
|
|
244
|
+
try:
|
|
245
|
+
return error_body.validate_python(error.details)
|
|
246
|
+
except ValidationError:
|
|
247
|
+
return None
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Use the returned parsed body on success and retain the original exception for
|
|
251
|
+
fallback handling. This alias describes an object response, so `None` can
|
|
252
|
+
represent failure here; for a nullable body alias, use a separate success flag
|
|
253
|
+
or sentinel. Neither recipe changes SDK defaults or automatically validates all
|
|
254
|
+
HTTP failures. Diagnostic reports correctly retain `consumer_controlled` for
|
|
255
|
+
HTTP-error body validation.
|
|
256
|
+
|
|
198
257
|
The checked consumer examples show HTTP validation errors, fallback handling,
|
|
199
258
|
stream parsing/transport errors, and ordinary application error events:
|
|
200
259
|
|
|
@@ -117,6 +117,35 @@ generated files without deleting neighboring handwritten files. Use a dedicated
|
|
|
117
117
|
output directory for each language. Unsafe paths and generated-name collisions
|
|
118
118
|
fail explicitly.
|
|
119
119
|
|
|
120
|
+
### SDK provenance
|
|
121
|
+
|
|
122
|
+
CLI generation always writes `.fastapi-streams.json` alongside the SDK. Commit
|
|
123
|
+
this file with the generated sources. Its existing `files` list still controls
|
|
124
|
+
ownership and cleanup; the additional `generation` object contains the same
|
|
125
|
+
provenance as an optional diagnostic report:
|
|
126
|
+
|
|
127
|
+
| Field | Meaning |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `generator`, `generator_version` | Generator distribution and version |
|
|
130
|
+
| `schema_sha256` | SHA-256 of the complete source OpenAPI document |
|
|
131
|
+
| `schema_hash_encoding` | `canonical-json-v1`: JSON with recursively sorted keys, compact separators and UTF-8 without ASCII escaping |
|
|
132
|
+
| `target` | `python` or `typescript` |
|
|
133
|
+
| `python_http_client` | `httpx` / `httpx2` for Python, JSON null for TypeScript |
|
|
134
|
+
|
|
135
|
+
There are no timestamps or local paths. Formatting and object-key order do not
|
|
136
|
+
affect the schema hash; changes to any schema content do, including descriptions
|
|
137
|
+
and components unused by the SDK. This identifies the input and configuration;
|
|
138
|
+
it is not a signature or an integrity check of the generated sources.
|
|
139
|
+
|
|
140
|
+
`check` compares the manifest as well as SDK sources. A different generator
|
|
141
|
+
version or source hash is drift even when the emitted code is identical.
|
|
142
|
+
Regenerate once with the chosen language and existing HTTP backend to upgrade a
|
|
143
|
+
legacy files-only manifest, then commit the manifest diff. Checks remain
|
|
144
|
+
read-only; generation retains neighboring handwritten files and only removes
|
|
145
|
+
previously owned stale files. No diagnostic report is required for provenance.
|
|
146
|
+
The low-level `write_files` API accepts optional `generation` metadata; without
|
|
147
|
+
it, its files-only behavior remains available for callers writing raw file maps.
|
|
148
|
+
|
|
120
149
|
## Runtime validation and unknown variants
|
|
121
150
|
|
|
122
151
|
Python validates path, query and header parameters and JSON request bodies at the
|
|
@@ -8,6 +8,8 @@ from examples.agent_runs.generated_python import (
|
|
|
8
8
|
)
|
|
9
9
|
from examples.agent_runs.generated_python.models import StreamRunEventsErrorBody
|
|
10
10
|
|
|
11
|
+
_ERROR_BODY = TypeAdapter(StreamRunEventsErrorBody)
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
async def consume_with_errors(client: Client, run_id: str) -> None:
|
|
13
15
|
try:
|
|
@@ -19,9 +21,7 @@ async def consume_with_errors(client: Client, run_id: str) -> None:
|
|
|
19
21
|
except HttpError as error:
|
|
20
22
|
if error.status == 422:
|
|
21
23
|
try:
|
|
22
|
-
body =
|
|
23
|
-
error.details
|
|
24
|
-
)
|
|
24
|
+
body = _ERROR_BODY.validate_python(error.details)
|
|
25
25
|
except ValidationError:
|
|
26
26
|
pass
|
|
27
27
|
else:
|
|
@@ -15,8 +15,9 @@ export async function consumeWithErrors(
|
|
|
15
15
|
}
|
|
16
16
|
} catch (error: unknown) {
|
|
17
17
|
if (options.signal?.aborted) throw error;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const body = decodeValidationError(error);
|
|
19
|
+
if (body !== undefined) {
|
|
20
|
+
console.error("Invalid stream parameters", body.detail ?? []);
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
22
23
|
if (error instanceof HttpError) {
|
|
@@ -30,6 +31,12 @@ export async function consumeWithErrors(
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
// Consumer-local decoder: scope the generated body alias to this operation/status.
|
|
35
|
+
export function decodeValidationError(error: unknown): StreamRunEventsErrorBody | undefined {
|
|
36
|
+
if (isHttpError(error, isValidationErrorBody) && error.status === 422) return error.details;
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
function isValidationErrorBody(value: unknown): value is StreamRunEventsErrorBody {
|
|
34
41
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
35
42
|
if (!("detail" in value)) return true;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
"agent-runs/api.ts",
|
|
4
|
+
"agent-runs/index.ts",
|
|
5
|
+
"client.ts",
|
|
6
|
+
"index.ts",
|
|
7
|
+
"transport/errors.ts",
|
|
8
|
+
"transport/sse.ts",
|
|
9
|
+
"types.ts"
|
|
10
|
+
],
|
|
11
|
+
"generation": {
|
|
12
|
+
"generator": "fastapi-streams",
|
|
13
|
+
"generator_version": "0.4.0",
|
|
14
|
+
"python_http_client": null,
|
|
15
|
+
"schema_hash_encoding": "canonical-json-v1",
|
|
16
|
+
"schema_sha256": "f23def53557e9d381bada00de07054f0a105eceaefdf5f47eb3168a0e8a05c7f",
|
|
17
|
+
"target": "typescript"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
"__init__.py",
|
|
4
|
+
"agent_runs/__init__.py",
|
|
5
|
+
"agent_runs/api.py",
|
|
6
|
+
"client.py",
|
|
7
|
+
"models.py",
|
|
8
|
+
"py.typed",
|
|
9
|
+
"runtime.py"
|
|
10
|
+
],
|
|
11
|
+
"generation": {
|
|
12
|
+
"generator": "fastapi-streams",
|
|
13
|
+
"generator_version": "0.4.0",
|
|
14
|
+
"python_http_client": "httpx",
|
|
15
|
+
"schema_hash_encoding": "canonical-json-v1",
|
|
16
|
+
"schema_sha256": "f23def53557e9d381bada00de07054f0a105eceaefdf5f47eb3168a0e8a05c7f",
|
|
17
|
+
"target": "python"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -106,6 +106,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
106
106
|
if not isinstance(document, dict):
|
|
107
107
|
raise ContractError("The OpenAPI document must be an object")
|
|
108
108
|
document = cast(Document, document)
|
|
109
|
+
generation = _generation_metadata(
|
|
110
|
+
document, args.language, args.python_http_client or "httpx"
|
|
111
|
+
)
|
|
109
112
|
if args.command == "check" and args.app:
|
|
110
113
|
expected = export_schema(_load_app(args.app))
|
|
111
114
|
if expected != document:
|
|
@@ -122,9 +125,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
122
125
|
args.diagnostics,
|
|
123
126
|
diagnostics,
|
|
124
127
|
set(),
|
|
125
|
-
|
|
126
|
-
args.language,
|
|
127
|
-
args.python_http_client or "httpx",
|
|
128
|
+
generation,
|
|
128
129
|
)
|
|
129
130
|
accepted: set[str] = (
|
|
130
131
|
accepted_diagnostics(
|
|
@@ -138,9 +139,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
138
139
|
args.diagnostics,
|
|
139
140
|
diagnostics,
|
|
140
141
|
accepted,
|
|
141
|
-
|
|
142
|
-
args.language,
|
|
143
|
-
args.python_http_client or "httpx",
|
|
142
|
+
generation,
|
|
144
143
|
)
|
|
145
144
|
pending = [item for item in diagnostics if item.id not in accepted]
|
|
146
145
|
emit_diagnostics(pending)
|
|
@@ -156,7 +155,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
156
155
|
if args.language == "python"
|
|
157
156
|
else generate_typescript(contract)
|
|
158
157
|
)
|
|
159
|
-
changed = write_files(
|
|
158
|
+
changed = write_files(
|
|
159
|
+
args.output, generated, check=args.check, generation=generation
|
|
160
|
+
)
|
|
160
161
|
if args.check and changed:
|
|
161
162
|
for path in changed:
|
|
162
163
|
print(f"Out of date: {path}", file=sys.stderr)
|
|
@@ -182,33 +183,37 @@ def _load_app(value: str) -> FastAPI:
|
|
|
182
183
|
return app
|
|
183
184
|
|
|
184
185
|
|
|
186
|
+
def _generation_metadata(
|
|
187
|
+
document: Document, target: str, http_client: str
|
|
188
|
+
) -> dict[str, str | None]:
|
|
189
|
+
# Share canonical provenance between manifests and optional reports.
|
|
190
|
+
encoded = json.dumps(
|
|
191
|
+
document, sort_keys=True, separators=(",", ":"), ensure_ascii=False
|
|
192
|
+
).encode("utf-8")
|
|
193
|
+
return {
|
|
194
|
+
"generator": "fastapi-streams",
|
|
195
|
+
"generator_version": __version__,
|
|
196
|
+
"schema_sha256": hashlib.sha256(encoded).hexdigest(),
|
|
197
|
+
"schema_hash_encoding": "canonical-json-v1",
|
|
198
|
+
"target": target,
|
|
199
|
+
"python_http_client": http_client if target == "python" else None,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
185
203
|
def _write_diagnostics(
|
|
186
204
|
path: Path,
|
|
187
205
|
diagnostics: list[GenerationDiagnostic],
|
|
188
206
|
accepted: set[str],
|
|
189
|
-
|
|
190
|
-
target: str,
|
|
191
|
-
http_client: str,
|
|
207
|
+
generation: dict[str, str | None],
|
|
192
208
|
) -> None:
|
|
193
|
-
|
|
194
|
-
encoded = json.dumps(
|
|
195
|
-
document, sort_keys=True, separators=(",", ":"), ensure_ascii=False
|
|
196
|
-
).encode("utf-8")
|
|
197
|
-
python = target == "python"
|
|
209
|
+
python = generation["target"] == "python"
|
|
198
210
|
request_phase = "method_call" if python else "not_performed"
|
|
199
211
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
200
212
|
path.write_text(
|
|
201
213
|
json.dumps(
|
|
202
214
|
{
|
|
203
215
|
"version": 1,
|
|
204
|
-
"generation":
|
|
205
|
-
"generator": "fastapi-streams",
|
|
206
|
-
"generator_version": __version__,
|
|
207
|
-
"schema_sha256": hashlib.sha256(encoded).hexdigest(),
|
|
208
|
-
"schema_hash_encoding": "canonical-json-v1",
|
|
209
|
-
"target": target,
|
|
210
|
-
"python_http_client": http_client if python else None,
|
|
211
|
-
},
|
|
216
|
+
"generation": generation,
|
|
212
217
|
"validation": {
|
|
213
218
|
"full_json_schema": False,
|
|
214
219
|
"runtime_scope": (
|
|
@@ -8,7 +8,11 @@ MANIFEST = ".fastapi-streams.json"
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def write_files(
|
|
11
|
-
output: Path,
|
|
11
|
+
output: Path,
|
|
12
|
+
files: Mapping[str, str],
|
|
13
|
+
*,
|
|
14
|
+
check: bool = False,
|
|
15
|
+
generation: Mapping[str, str | None] | None = None,
|
|
12
16
|
) -> tuple[Path, ...]:
|
|
13
17
|
"""Write deterministic output; only remove stale files owned by the manifest.
|
|
14
18
|
|
|
@@ -17,7 +21,10 @@ def write_files(
|
|
|
17
21
|
expected = dict(files)
|
|
18
22
|
if MANIFEST in expected:
|
|
19
23
|
raise ValueError(f"{MANIFEST} is reserved")
|
|
20
|
-
|
|
24
|
+
metadata: dict[str, Any] = {"files": sorted(files)}
|
|
25
|
+
if generation is not None:
|
|
26
|
+
metadata["generation"] = dict(generation)
|
|
27
|
+
expected[MANIFEST] = json.dumps(metadata, indent=2, sort_keys=True) + "\n"
|
|
21
28
|
manifest = _owned_path(output, MANIFEST)
|
|
22
29
|
previous: set[str] = set()
|
|
23
30
|
if manifest.exists():
|
|
@@ -133,6 +133,11 @@ def main() -> None:
|
|
|
133
133
|
args.http_client if language == "python" else None
|
|
134
134
|
)
|
|
135
135
|
assert len(report["generation"]["schema_sha256"]) == 64
|
|
136
|
+
manifest = json.loads(
|
|
137
|
+
(working / output / ".fastapi-streams.json").read_text()
|
|
138
|
+
)
|
|
139
|
+
assert manifest["generation"] == report["generation"]
|
|
140
|
+
assert client_file in manifest["files"]
|
|
136
141
|
assert report["validation"]["full_json_schema"] is False
|
|
137
142
|
assert report["validation"]["events"] == {
|
|
138
143
|
"phase": "iteration",
|
|
@@ -11,6 +11,44 @@ from fastapi_streams.codegen.typescript import generate_typescript
|
|
|
11
11
|
from fastapi_streams.codegen.writer import MANIFEST, write_files
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
@pytest.mark.parametrize(
|
|
15
|
+
"target,backend", [("python", "httpx"), ("python", "httpx2"), ("typescript", None)]
|
|
16
|
+
)
|
|
17
|
+
def test_cli_upgrades_legacy_manifest_without_a_diagnostic_report(
|
|
18
|
+
tmp_path, target, backend
|
|
19
|
+
):
|
|
20
|
+
schema, output = tmp_path / "schema.json", tmp_path / "sdk"
|
|
21
|
+
document = export_schema(app)
|
|
22
|
+
schema.write_text(json.dumps(document), encoding="utf-8")
|
|
23
|
+
write_files(output, {"stale.txt": "old"})
|
|
24
|
+
handwritten = output / "handwritten.txt"
|
|
25
|
+
handwritten.write_text("keep", encoding="utf-8")
|
|
26
|
+
flags = ["--schema", str(schema), "--language", target, "--output", str(output)]
|
|
27
|
+
if backend:
|
|
28
|
+
flags += ["--python-http-client", backend]
|
|
29
|
+
legacy = (output / MANIFEST).read_bytes()
|
|
30
|
+
assert main(["check", *flags]) == 1
|
|
31
|
+
assert (output / MANIFEST).read_bytes() == legacy
|
|
32
|
+
assert main(["generate", *flags]) == 0
|
|
33
|
+
manifest = json.loads((output / MANIFEST).read_bytes())
|
|
34
|
+
assert manifest["generation"]["target"] == target
|
|
35
|
+
assert manifest["generation"]["python_http_client"] == backend
|
|
36
|
+
assert manifest["files"] == sorted(
|
|
37
|
+
path.relative_to(output).as_posix()
|
|
38
|
+
for path in output.rglob("*")
|
|
39
|
+
if path.is_file() and path.name not in {MANIFEST, "handwritten.txt"}
|
|
40
|
+
)
|
|
41
|
+
assert not (output / "stale.txt").exists()
|
|
42
|
+
assert handwritten.read_text() == "keep"
|
|
43
|
+
assert main(["check", *flags]) == 0
|
|
44
|
+
# A different generator version is drift even if source files are identical.
|
|
45
|
+
manifest["generation"]["generator_version"] = "0.0.1"
|
|
46
|
+
(output / MANIFEST).write_text(json.dumps(manifest), encoding="utf-8")
|
|
47
|
+
stale = (output / MANIFEST).read_bytes()
|
|
48
|
+
assert main(["check", *flags]) == 1
|
|
49
|
+
assert (output / MANIFEST).read_bytes() == stale
|
|
50
|
+
|
|
51
|
+
|
|
14
52
|
def test_ir_and_types_are_typed_and_streams_only():
|
|
15
53
|
contract = build_ir(export_schema(app))
|
|
16
54
|
assert {op.operation_id for op in contract.operations} == {
|
|
@@ -5,15 +5,13 @@ import httpx
|
|
|
5
5
|
import pytest
|
|
6
6
|
from pydantic import TypeAdapter, ValidationError
|
|
7
7
|
|
|
8
|
+
from fastapi_streams.codegen.cli import main
|
|
8
9
|
from fastapi_streams.codegen.ir import build_ir
|
|
9
10
|
from fastapi_streams.codegen.openapi import (
|
|
10
11
|
ContractError,
|
|
11
12
|
export_schema,
|
|
12
13
|
validate_contract,
|
|
13
14
|
)
|
|
14
|
-
from fastapi_streams.codegen.python import generate_python
|
|
15
|
-
from fastapi_streams.codegen.typescript import generate_typescript
|
|
16
|
-
from fastapi_streams.codegen.writer import write_files
|
|
17
15
|
from tests.fixtures.generated_python import Client
|
|
18
16
|
from tests.fixtures.generated_python.models import (
|
|
19
17
|
FixtureEvent,
|
|
@@ -76,13 +74,25 @@ def test_fixture_generation_is_current():
|
|
|
76
74
|
document = export_schema(app)
|
|
77
75
|
snapshot = Path("tests/fixtures/models.openapi.json")
|
|
78
76
|
assert json.loads(snapshot.read_text()) == document
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
for language, directory in (
|
|
78
|
+
("typescript", "generated"),
|
|
79
|
+
("python", "generated_python"),
|
|
80
|
+
):
|
|
81
|
+
assert (
|
|
82
|
+
main(
|
|
83
|
+
[
|
|
84
|
+
"check",
|
|
85
|
+
"--schema",
|
|
86
|
+
str(snapshot),
|
|
87
|
+
"--language",
|
|
88
|
+
language,
|
|
89
|
+
"--output",
|
|
90
|
+
f"tests/fixtures/{directory}",
|
|
91
|
+
"--strict",
|
|
92
|
+
]
|
|
93
|
+
)
|
|
94
|
+
== 0
|
|
95
|
+
)
|
|
86
96
|
|
|
87
97
|
|
|
88
98
|
def test_refs_in_properties_named_like_schema_annotations_are_checked():
|
|
@@ -2,7 +2,6 @@ import asyncio
|
|
|
2
2
|
import importlib.util
|
|
3
3
|
import sys
|
|
4
4
|
from enum import StrEnum
|
|
5
|
-
from pathlib import Path
|
|
6
5
|
|
|
7
6
|
import httpx
|
|
8
7
|
import pytest
|
|
@@ -22,7 +21,6 @@ from fastapi_streams.codegen.cli import main
|
|
|
22
21
|
from fastapi_streams.codegen.ir import build_ir
|
|
23
22
|
from fastapi_streams.codegen.openapi import export_schema
|
|
24
23
|
from fastapi_streams.codegen.python import generate_python
|
|
25
|
-
from fastapi_streams.codegen.writer import write_files
|
|
26
24
|
|
|
27
25
|
|
|
28
26
|
class Chunks(httpx.AsyncByteStream):
|
|
@@ -379,8 +377,20 @@ def test_python_codegen_cli_and_snapshot(tmp_path):
|
|
|
379
377
|
contract = build_ir(export_schema(app))
|
|
380
378
|
files = generate_python(contract)
|
|
381
379
|
assert files == generate_python(contract)
|
|
382
|
-
assert
|
|
383
|
-
|
|
380
|
+
assert (
|
|
381
|
+
main(
|
|
382
|
+
[
|
|
383
|
+
"check",
|
|
384
|
+
"--schema",
|
|
385
|
+
"tests/fixtures/agent_runs.openapi.json",
|
|
386
|
+
"--language",
|
|
387
|
+
"python",
|
|
388
|
+
"--output",
|
|
389
|
+
"examples/agent_runs/generated_python",
|
|
390
|
+
"--strict",
|
|
391
|
+
]
|
|
392
|
+
)
|
|
393
|
+
== 0
|
|
384
394
|
)
|
|
385
395
|
schema = tmp_path / "schema.json"
|
|
386
396
|
assert (
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_structured_diagnostics.py
RENAMED
|
@@ -237,6 +237,9 @@ def test_report_describes_actual_target_phases_and_stable_schema_content(
|
|
|
237
237
|
original = report.read_bytes()
|
|
238
238
|
result = json.loads(original)
|
|
239
239
|
generation = result["generation"]
|
|
240
|
+
manifest_path = tmp_path / "sdk" / ".fastapi-streams.json"
|
|
241
|
+
manifest_bytes = manifest_path.read_bytes()
|
|
242
|
+
assert json.loads(manifest_bytes)["generation"] == generation
|
|
240
243
|
assert generation["generator"] == "fastapi-streams"
|
|
241
244
|
assert generation["generator_version"] == __version__
|
|
242
245
|
assert generation["target"] == target
|
|
@@ -266,11 +269,18 @@ def test_report_describes_actual_target_phases_and_stable_schema_content(
|
|
|
266
269
|
)
|
|
267
270
|
assert main(["check", *flags]) == 0
|
|
268
271
|
assert report.read_bytes() == original
|
|
272
|
+
assert manifest_path.read_bytes() == manifest_bytes
|
|
269
273
|
# The complete document participates, including components not emitted by SDKs.
|
|
270
274
|
document["info"]["description"] = "Changed contract provenance"
|
|
271
275
|
schema.write_text(json.dumps(document), encoding="utf-8")
|
|
272
|
-
assert main(["check", *flags]) ==
|
|
276
|
+
assert main(["check", *flags]) == 1
|
|
277
|
+
assert manifest_path.read_bytes() == manifest_bytes
|
|
273
278
|
assert (
|
|
274
279
|
json.loads(report.read_bytes())["generation"]["schema_sha256"]
|
|
275
280
|
!= generation["schema_sha256"]
|
|
276
281
|
)
|
|
282
|
+
assert main(["generate", *flags]) == 0
|
|
283
|
+
assert (
|
|
284
|
+
json.loads(manifest_path.read_bytes())["generation"]
|
|
285
|
+
== json.loads(report.read_bytes())["generation"]
|
|
286
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
"client.ts",
|
|
4
|
+
"fixtures/api.ts",
|
|
5
|
+
"fixtures/index.ts",
|
|
6
|
+
"index.ts",
|
|
7
|
+
"transport/errors.ts",
|
|
8
|
+
"transport/sse.ts",
|
|
9
|
+
"types.ts"
|
|
10
|
+
],
|
|
11
|
+
"generation": {
|
|
12
|
+
"generator": "fastapi-streams",
|
|
13
|
+
"generator_version": "0.4.0",
|
|
14
|
+
"python_http_client": null,
|
|
15
|
+
"schema_hash_encoding": "canonical-json-v1",
|
|
16
|
+
"schema_sha256": "b12c8dd481b9c592222dc45a26586aecc8899702e42ccc0deed834d91a7cfe41",
|
|
17
|
+
"target": "typescript"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
"__init__.py",
|
|
4
|
+
"client.py",
|
|
5
|
+
"fixtures/__init__.py",
|
|
6
|
+
"fixtures/api.py",
|
|
7
|
+
"models.py",
|
|
8
|
+
"py.typed",
|
|
9
|
+
"runtime.py"
|
|
10
|
+
],
|
|
11
|
+
"generation": {
|
|
12
|
+
"generator": "fastapi-streams",
|
|
13
|
+
"generator_version": "0.4.0",
|
|
14
|
+
"python_http_client": "httpx",
|
|
15
|
+
"schema_hash_encoding": "canonical-json-v1",
|
|
16
|
+
"schema_sha256": "b12c8dd481b9c592222dc45a26586aecc8899702e42ccc0deed834d91a7cfe41",
|
|
17
|
+
"target": "python"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { test } from "node:test";
|
|
3
3
|
import { createClient, HttpError, isHttpError, TransportError } from "../../examples/agent_runs/generated/index.js";
|
|
4
|
-
import { consumeWithErrors } from "../../examples/agent_runs/errors.js";
|
|
4
|
+
import { consumeWithErrors, decodeValidationError } from "../../examples/agent_runs/errors.js";
|
|
5
5
|
|
|
6
6
|
type MissingProblem = {
|
|
7
7
|
code: "missing";
|
|
@@ -67,3 +67,18 @@ test("consumer example handles validated HTTP errors and rethrows unexpected bod
|
|
|
67
67
|
await assert.rejects(() => consumeWithErrors(makeClient(unexpected), "demo"), HttpError);
|
|
68
68
|
}
|
|
69
69
|
});
|
|
70
|
+
|
|
71
|
+
test("consumer decoder retains generic failures and checks the actual status", () => {
|
|
72
|
+
const body = { detail: [{ loc: ["run_id", 0], msg: "Invalid", type: "value_error" }] };
|
|
73
|
+
const response = new Response(null, { status: 422 });
|
|
74
|
+
const expected = new HttpError(422, body, response);
|
|
75
|
+
assert.equal(decodeValidationError(expected), body);
|
|
76
|
+
for (const details of ["Bad gateway", undefined, null, [], { detail: [{}] }]) {
|
|
77
|
+
const error = new HttpError(422, details, response);
|
|
78
|
+
assert.equal(decodeValidationError(error), undefined);
|
|
79
|
+
assert.equal(error.details, details);
|
|
80
|
+
assert.equal(error.response, response);
|
|
81
|
+
}
|
|
82
|
+
assert.equal(decodeValidationError(new HttpError(502, body, response)), undefined);
|
|
83
|
+
assert.equal(decodeValidationError(new TransportError("offline")), undefined);
|
|
84
|
+
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated/agent-runs/api.ts
RENAMED
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated/agent-runs/index.ts
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated/transport/errors.ts
RENAMED
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated/transport/sse.ts
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated_python/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated_python/client.py
RENAMED
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated_python/models.py
RENAMED
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated_python/py.typed
RENAMED
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/examples/agent_runs/generated_python/runtime.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/schemas/openapi-3.2.json
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/templates/python/api.py.j2
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/fastapi_streams/codegen/templates/python/runtime.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/codegen/test_stream_validation_details.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated/transport/errors.ts
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated_python/fixtures/__init__.py
RENAMED
|
File without changes
|
{fastapi_streams-0.3.1 → fastapi_streams-0.4.0}/tests/fixtures/generated_python/fixtures/api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|