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
Problem
Pydantic doesn't auto-call custom
model_dump()on nested models — if a parent's children havemodel_dump()overrides, the parent'ssuper().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 withexclude=True, or AdCP-spec-shaped overrides on extended types) end up writing this in every response model: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)
AdCPBaseModelrecursively dispatches custommodel_dump()on children. Walk__pydantic_fields__, detect when a field's annotated type has a custommodel_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