Skip to content

[BUG](codegen) Unwrap RootModel fields to their bare root shape - #590

Merged
Victor Schappert (vcschapp) merged 1 commit into
mainfrom
fix-rootmodel-shape
Jul 22, 2026
Merged

[BUG](codegen) Unwrap RootModel fields to their bare root shape#590
Victor Schappert (vcschapp) merged 1 commit into
mainfrom
fix-rootmodel-shape

Conversation

@sethfitz

@sethfitz Seth Fitzsimmons (sethfitz) commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #583. A field typed as a pydantic.RootModel subclass was extracted by the PySpark codegen as a struct with a synthetic root member, so the generated Spark schema declared struct<root: ...> while the Parquet data carries the bare root value. The mismatch surfaces only when validating real data.

Root cause

RootModel is a BaseModel subclass, so in extraction/type_analyzer.py a RootModel-typed field reached the model terminal and resolved to a ModelRef — a struct whose one member is root.

Fix

analyze_type's _unwrap now intercepts a RootModel before terminal classification: it recurses into the root field's annotation and reattaches any root-level metadata via a shared attach_field_metadata helper, extracted here from model_extraction so field-level and root-level metadata share one implementation. A RootModel[dict[str, int]] field extracts to a bare MapOf, rendering as MapType(...) downstream — resolver or not.

No current schema model uses RootModel, so no generated output changes. This is a latent-bug fix that makes the codegen correct for RootModel-typed fields.

A field typed as a RootModel subclass was extracted as a struct with a
synthetic `root` member. RootModel is a BaseModel subclass, so it
reached the model terminal and resolved to a ModelRef; the generated
Spark schema declared `struct<root: ...>` while the Parquet data carries
the bare root value.

`analyze_type` now intercepts a RootModel before terminal
classification, recursing into the root field's annotation and
reattaching any root-level metadata onto the unwrapped layer. A
`RootModel[dict[str, int]]` field becomes a bare `MapOf`, rendering as
`MapType(...)` downstream.

The reattach step reuses `attach_field_metadata`, extracted here into
`type_analyzer` from `model_extraction` so field-level and root-level
metadata share one implementation.

Fixes #583.

Signed-off-by: Seth Fitzsimmons <seth@mojodna.net>
@github-actions

Copy link
Copy Markdown

🗺️ Schema reference docs preview is live!

🌍 Preview https://staging.overturemaps.org/schema/pr/590/schema/index.html
🕐 Updated Jul 22, 2026 16:50 UTC
📝 Commit b129c25
🔧 env SCHEMA_PREVIEW true

Note

♻️ This preview updates automatically with each push to this PR.

@RoelBollens-TomTom

Copy link
Copy Markdown
Collaborator

Looks good, just one remark, when you have a discovered model via entry point rather than used as a field it will still produces a RecordSpec with the root field. I'll leave it up to you if you want to harden against that already as RootModels as entry points could already cause additional issues upstream.

@vcschapp
Victor Schappert (vcschapp) merged commit 8424054 into main Jul 22, 2026
24 of 25 checks passed
@vcschapp
Victor Schappert (vcschapp) deleted the fix-rootmodel-shape branch July 22, 2026 23:25
@sethfitz

Copy link
Copy Markdown
Collaborator Author

Good call on the entry point complication. I’ll have a follow-up PR up later this week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RootModel is wrongly shaped by the PySpark codegen

3 participants