feat(v2): type the parse ParseResponse structure & grounding trees#108
Conversation
The V2 gateway now publishes a typed ParseResponse for POST /v2/parse, so
model `structure` and `grounding` as proper trees instead of loose
`object`s:
- structure: V2ParseStructure -> V2ParsePage -> V2ParseElement
- grounding: V2ParseGrounding -> V2ParseGroundingPage ->
V2ParseGroundingElement -> V2ParseGroundingEntry
Callers get attribute access (page.width, cell.row/col, element.box,
part.span) with IDE autocomplete instead of raw dicts.
Kept deliberately permissive to avoid deserialization failures on drift:
element `type` and page `status` are plain `str` (not Literal), spans/boxes
are `List[int]`, and BaseModel's `extra="allow"` retains unknown keys.
`structure`/`grounding` stay Optional. Not breaking: client.v2 is
unreleased, and these are additive typed-surface changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a typed ParseResponse surface for client.v2.parse() by replacing the previously loose structure/grounding objects with explicit, recursive tree models that mirror the gateway schema, and updates tests/docs accordingly.
Changes:
- Introduces typed
V2ParseStructure/V2ParseGroundingmodel trees (document → page → element/entry) onV2ParseResponse. - Updates V2 parse tests to validate typed attribute access and tolerance for unknown element types/extra keys.
- Exposes the new models from
landingai_ade.types.v2and documents them inapi.md.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/landingai_ade/types/v2/parse_response.py |
Defines the new typed structure/grounding tree models and updates V2ParseResponse to use them. |
src/landingai_ade/types/v2/__init__.py |
Re-exports the new parse/grounding model types from the v2 types package. |
tests/test_v2_types.py |
Updates the parse response type test to build from dicts matching the new typed shape. |
tests/api_resources/v2/test_parse.py |
Adds richer mocked parse responses and assertions for typed access and tolerance behavior. |
api.md |
Documents the newly exported parse/grounding tree types and their relationships. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Addresses PR review: replace mutable `= []` defaults on the structure/ grounding list fields with `Field(default_factory=list)`, matching the repo convention (e.g. Job.raw). No functional change — the construct_type fast path already copies defaults per instance (verified) — but this is the idiomatic, future-proof form. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the review. All six comments point at the same thing — mutable Verification first: the specific "shared list across instances" leak doesn't reproduce on this SDK's Applied anyway because it's the right convention: switched all five non-optional list fields ( Tests + |
The Field(default_factory=list) form trips the repo's strict pyright with "Type of children is partially unknown" (bare list -> list[Unknown] vs the List[TypedModel] annotation). Job.raw only gets away with default_factory because its value type is `object`, which absorbs Unknown. Reverting to `= []`: verified pyright-clean, and the shared-mutable-default leak the review flagged does not reproduce on this SDK's construct_type path (each instance gets a fresh, independent list). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Correction to my previous comment: I initially applied So I've reverted to literal
Net: the review's underlying concern (cross-instance list sharing) is real in general but doesn't manifest here, and the suggested fix conflicts with the type-checker config — so the literal default is the correct form for this codebase. |
Addresses PR review: the tolerance test claimed extra keys are retained but never asserted it. Now checks that `future_field` survives on the deserialized page (extra="allow"). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
The V2 gateway now publishes a typed
ParseResponseforPOST /v2/parse(confirmed against the updated dev OpenAPI spec). Previouslystructureandgroundingwere modeled as looseOptional[object](raw dicts). This PR models them as proper typed trees so callers get attribute access + autocomplete instead of dict indexing.Follow-up to #106 (which covered
service_tier+billing).New models (
types/v2/parse_response.py)structureOptional[object]V2ParseStructure→V2ParsePage→V2ParseElementgroundingOptional[object]V2ParseGrounding→V2ParseGroundingPage→V2ParseGroundingElement→V2ParseGroundingEntryV2ParseElementandV2ParseGroundingElementare self-recursive (atable'schildrenare itstable_cells).Why it's safe / not breaking
client.v2is unreleased — additive typed surface, no released consumers.typeand pagestatusare plainstr(notLiteral), spans/boxes areList[int], all non-required fields default, andBaseModel'sextra="allow"keeps unknown keys.structure/groundingremainOptional.Note on tolerance
The old
test_parse_response_tolerates_loose_shapefedstructure=[{"type":"text"}](a bare list) to assert the maximally-looseobjectcontract. That contract is what we're intentionally tightening, so the test was updated (test_parse_response_builds_from_dicts) to build from the real typed shape while still covering metadata/billing tolerance and unknown-key retention.Test plan
structure/groundingtyped access; unknown elementtype+ extra keys toleratedruff check .,mypy .(77 files), andimport landingai_adeall clean🤖 Generated with Claude Code