Skip to content

feat(types): nested model_dump() resolution in response models #615

@bokelley

Description

@bokelley

Problem

Pydantic doesn't auto-call custom model_dump() on nested models — if a parent's children have model_dump() overrides, the parent's super().model_dump() produces the children via the default Pydantic serialization, not via the children's overrides. Adopters that need custom serialization on a child class (most commonly: tenant-internal fields excluded with exclude=True, or AdCP-spec-shaped overrides on extended types) end up writing this in every response model:

class GetCreativesResponse(AdCPBaseModel):
    creatives: list[Creative]

    def model_dump(self, **kwargs):
        result = super().model_dump(**kwargs)
        if \"creatives\" in result and self.creatives:
            result[\"creatives\"] = [c.model_dump(**kwargs) for c in self.creatives]
        return result

We have ~59 such overrides across src/core/schemas/. Every override is mechanical: walk the children, call their model_dump. Adding a new response type means writing one again. Forgetting one is silent — the test fails on a serialization-difference, but only if the test exercises the nested path.

Proposed SDK shape

Either of:

A) AdCPBaseModel recursively dispatches custom model_dump() on children. Walk __pydantic_fields__, detect when a field's annotated type has a custom model_dump, and call it directly instead of relying on Pydantic's default child serialization. Same for lists/dicts of models.

B) Codegen flag that emits nested-aware model_dump() on the generated response types — adopters extending those types via inheritance get the nested-walking for free.

(A) is broader; (B) is narrower but ships with no risk to non-adopters.

Why this is the highest-LOC win on our open list

The other open framework gaps (spec defaults, agent-card URL, transport contextvar, test app builder) each kill 50-275 LOC of middleware. This one kills hundreds of LOC of mechanical override boilerplate spread across the schema package.

Files

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions