feat(python): emit response_type so generated calls deserialize into Pydantic models - #28
Merged
Merged
Conversation
…Pydantic models (firstlanding#6266) Every typed operation now passes response_type=Model (or list[Model], or the hoisted inline-response BaseModel) to the runtime's request(), making the generated return annotations true at runtime. A shared response-shape classifier (response-type.ts) drives both the resource emitter and the contract-tests emitter, so emitted behavior and test assertions cannot drift; generation fails loudly if a schema ref ever appears in a shape the classifier does not deserialize. Contract tests now assert concrete model classes (isinstance BaseModel plus type(result).__name__) instead of dict access. Also fixes a latent typegen bug surfaced by the new validation: fields named `object` shadowed the builtin during pydantic's deferred annotation resolution, turning dict[str, object] into dict[str, None]. Free-form types now emit typing.Any. Requires archastro-python runtime support for response_type (companion PR) — merge that first; publish this generator version after both land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
✅ Clean PR, @rob-archastro! No blocking findings on
Reply |
calvin-archastro
added a commit
that referenced
this pull request
Jun 30, 2026
…ckend (TS + Python) Previously emitStreamContractTestFile was only wired into the channel-harness sample — so SDK regens (contract-tests-ts/py) shipped the generated stream() methods with no contract test. Wire SSE coverage into generateContractTests itself, for both backends. - method-chain-builder: add buildStreamCalls (the streaming inverse of buildMethodCalls), sharing the resource walk so streaming ops get the same accessor chains (client.v1.ai.chat.completions.stream). - typescript-emitter: emit __tests__/contract/streams/<resource>.contract.test.ts — construct PlatformClient pointed at the harness, registerStreamScenario with autoEmit (harness synthesizes contract-valid event payloads, resolving $refs), iterate stream(), assert event order; plus a non-2xx -> ApiError case. - python-emitter: emit tests/contract/streams/test_<resource>.py — same shape over AsyncPlatformClient.stream(). - Both gated by the existing opt-in env (ARCHASTRO_RUN_CHANNEL_CONTRACT_TESTS): excluded from the default test run + the harness subprocess boots when the spec has channels OR streams. These run green once their deps land: runtime streamSSE/stream_sse (archastro-js #34 / archastro-python #28) and registerStreamScenario on the harness client (TS: shipped in this PR's channel-harness; Python: phx_channel harness.py). Generator suite 299; channel-harness 63. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
calvin-archastro
added a commit
that referenced
this pull request
Jun 30, 2026
…ckend (TS + Python) Previously emitStreamContractTestFile was only wired into the channel-harness sample — so SDK regens (contract-tests-ts/py) shipped the generated stream() methods with no contract test. Wire SSE coverage into generateContractTests itself, for both backends. - method-chain-builder: add buildStreamCalls (the streaming inverse of buildMethodCalls), sharing the resource walk so streaming ops get the same accessor chains (client.v1.ai.chat.completions.stream). - typescript-emitter: emit __tests__/contract/streams/<resource>.contract.test.ts — construct PlatformClient pointed at the harness, registerStreamScenario with autoEmit (harness synthesizes contract-valid event payloads, resolving $refs), iterate stream(), assert event order; plus a non-2xx -> ApiError case. - python-emitter: emit tests/contract/streams/test_<resource>.py — same shape over AsyncPlatformClient.stream(). - Both gated by the existing opt-in env (ARCHASTRO_RUN_CHANNEL_CONTRACT_TESTS): excluded from the default test run + the harness subprocess boots when the spec has channels OR streams. These run green once their deps land: runtime streamSSE/stream_sse (archastro-js #34 / archastro-python #28) and registerStreamScenario on the harness client (TS: shipped in this PR's channel-harness; Python: phx_channel harness.py). Generator suite 299; channel-harness 63. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
The Python backend now emits
response_type=Model/list[Model]/ hoisted inlineBaseModelon every typed operation, in both sync and async resource classes, so generated return annotations are true at runtime (fixes firstlanding#6266; requires ArchAstro/archastro-python#20). One shared classifier (response-type.ts) drives the resource emitter and the contract-tests emitter — assertions and behavior cannot drift, the inline-model naming rule lives in one place, and generation fails loudly if a schema ref appears in a response shape the classifier doesn't deserialize. Contract tests assert concrete classes viatype(result).__name__. Also fixes a latent bug the new validation exposed: fields namedobjectshadow the builtin during pydantic's deferred annotation resolution (dict[str, object]silently becamedict[str, None]); free-form types now emittyping.Any, with a repro-backed regression test.sequenceDiagram participant S as OpenAPI spec participant RT as response-type classifier participant RE as resource emitter participant CE as contract-tests emitter S->>RE: OperationDef RE->>RT: pythonResponseTypeExpr(op) alt model or list shape RT-->>RE: expression like list of RoutinePreset RE->>RE: emit request call with response_type else ref buried in union or optional RT-->>RE: throws, generation fails end S->>CE: same OperationDef CE->>RT: pythonResponseShape(op) RT-->>CE: shape verdict CE->>CE: emit isinstance and class-name assertionsclassDiagram class ResponseTypeModule { pythonResponseShape(op) Shape pythonResponseTypeExpr(op, inlineName) string pythonInlineResponseName(className, opName) string } class ResourceEmitter { emitOperation() emitSyncOperation() } class ContractTestsEmitter { emitResultAssertions() } ResourceEmitter ..> ResponseTypeModule : uses ContractTestsEmitter ..> ResponseTypeModule : usesScope: tooling-only (SDK generator); no service code.
Risk: low-medium — output changes are mechanical and were verified by full local regeneration (byte-identical to the committed archastro-python output) plus 1,720 passing contract tests. Merge order matters once: ArchAstro/archastro-python#20 must merge before this version is published to npm, since the regenerate workflow installs
@latest.User impact: none until published + regenerated; then Python SDK responses become typed models.
Testing: 254 generator unit tests (9 new emission tests, 5 contract-assertion tests, shadowing regression, fail-loud throw test).
Follow-ups: version bump + npm publish after merge (release-process owner's call). After publish, running
Regenerate SDKon archastro-python should produce a zero diff — a free pipeline verification.🤖 Generated with Claude Code