Skip to content

Speakeasy generator bug: missing model_rebuild() for models with circular TYPE_CHECKING imports #186

Description

@devin-ai-integration

Problem

Speakeasy's Python generator uses TYPE_CHECKING imports to break circular schema references, but only emits model_rebuild() for the immediate forward-referenced model — not for models that transitively depend on it. This causes PydanticUserError: not fully defined at instantiation time for any model in the dependency chain.

Affected Schema

RowFilteringOperationNot has a recursive conditions field:

RowFilteringOperationNot:
  properties:
    conditions:
      type: array
      items:
        $ref: "#/components/schemas/RowFilteringOperation"  # circular
RowFilteringOperation:
  oneOf:
    - $ref: "#/components/schemas/RowFilteringOperationEqual"
    - $ref: "#/components/schemas/RowFilteringOperationNot"  # back-reference

The upstream spec marks this: x-airbyte-circular-ref: true.

Symptoms

Speakeasy generates RowFilteringOperationNot1 (with 1 suffix from name collision) and imports RowFilteringOperation under TYPE_CHECKING. It emits RowFilteringOperationNot1.model_rebuild() in __init__.py but NOT for dependent models. Result:

PydanticUserError: \`ConnectionResponse\` is not fully defined;
you should define \`RowFilteringOperationNot1\`, then call \`ConnectionResponse.model_rebuild()\`.

3 models affected: ConnectionsResponse, RowFilteringMapperConfiguration, StreamConfigurations.

Workaround

We use an OpenAPI overlay to break the circular $ref by pointing RowFilteringOperationNot.conditions.items directly at RowFilteringOperationEqual instead of RowFilteringOperation:

# overlays/python_speakeasy.yaml
actions:
  - target: "$.components.schemas.RowFilteringOperationNot.properties.conditions.items"
    update:
      $ref: "#/components/schemas/RowFilteringOperationEqual"

This removes the recursion (NOT(NOT(x)) = x, so nested NOT is redundant) and allows Speakeasy to generate clean direct imports without TYPE_CHECKING.

Investigation

Upstream Fix Needed

Speakeasy should emit model_rebuild() for ALL models that transitively depend on TYPE_CHECKING forward refs, not just the immediate one. This would fix the issue without needing the overlay workaround.

Related


Devin session

Reported by Aaron ("AJ") Steers (@aaronsteers), investigated and documented in Devin session.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions