Skip to content

fix: upgrade datamodel-code-generator to fix RootModel serialization bug#745

Merged
dmchoiboi merged 11 commits intomainfrom
fix/datamodel-codegen-rootmodel-defaults
Jan 10, 2026
Merged

fix: upgrade datamodel-code-generator to fix RootModel serialization bug#745
dmchoiboi merged 11 commits intomainfrom
fix/datamodel-codegen-rootmodel-defaults

Conversation

@dmchoiboi
Copy link
Copy Markdown
Collaborator

Summary

  • Upgrades datamodel-code-generator from >=0.25.8 to >=0.47.0 to fix a serialization bug
  • Regenerates OpenAPI types with the fixed generator

Problem

After commit 0cbb58d ("Update openai api schema #742"), the API started throwing errors:

AttributeError: 'str' object has no attribute 'root'

This happened during FastAPI response serialization for endpoints using the OpenAI-compatible API.

Root Cause

The datamodel-code-generator had a bug (Issue #2027) where RootModel fields with default values were generated with raw primitive defaults:

# Broken (old generator):
service_tier: Annotated[Optional[ServiceTier], Field()] = "auto"  # "auto" is a string, not ServiceTier

When Pydantic tries to serialize this, it expects a ServiceTier RootModel instance with a .root attribute, but gets a plain string.

Fix

The fix was merged in datamodel-code-generator PR #2714 and released in v0.47.0. It generates proper default_factory:

# Fixed (new generator):
service_tier: Annotated[ServiceTier | None, Field(default_factory=lambda: ServiceTier('auto'))]

Affected Fields

  • service_tier: ServiceTier (3 occurrences)
  • reasoning_effort: ReasoningEffort (5 occurrences)
  • search_context_size: WebSearchContextSize (1 occurrence)

Test plan

  • Verify the serialization error no longer occurs
  • Run existing tests to ensure no regressions

🤖 Generated with Claude Code

Upgrades datamodel-code-generator from >=0.25.8 to >=0.47.0 to fix a bug
where RootModel fields with default values were generated with raw primitive
defaults instead of proper RootModel instances.

This caused "'str' object has no attribute 'root'" errors during FastAPI
response serialization for fields like:
- service_tier: ServiceTier (3 occurrences)
- reasoning_effort: ReasoningEffort (5 occurrences)
- search_context_size: WebSearchContextSize (1 occurrence)

The fix (datamodel-code-generator PR #2714) generates proper default_factory:
```python
# Before (broken):
service_tier: Annotated[Optional[ServiceTier], Field()] = "auto"

# After (fixed):
service_tier: Annotated[ServiceTier | None, Field(default_factory=lambda: ServiceTier('auto'))]
```

Fixes serialization error introduced in commit 0cbb58d.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@lilyz-ai lilyz-ai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch!

dmchoiboi and others added 10 commits January 9, 2026 15:28
The new version of datamodel-code-generator now imports constr for
constrained string types. Add this export to the custom pydantic_types
module.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The newer datamodel-codegen changed CreateChatCompletionRequest.model
from str to ModelIdsShared (a RootModel). The override is still
compatible at runtime since ModelIdsShared wraps str.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The type field was optional without a default, which caused datamodel-codegen
to generate `Literal["message"] | None = None`. This broke Pydantic's
discriminated union since discriminator fields need concrete Literal types.

Adding `default: message` to the spec generates proper code with a non-None
default value.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The OpenAPI spec has InputMessage and OutputMessage both with type='message',
which causes Pydantic's discriminated union to fail with "Value 'message' mapped
to multiple choices". Removing the discriminator makes Pydantic try each model
in the union until one validates.

Also reverted the InputMessage.type default change since it wasn't needed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Handle all variations of the discriminator pattern in sed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ponseMessage

The OpenAPI spec marked these as required but the actual API doesn't always
return them (refusal is only present when model refuses). This caused
validation errors when deserializing responses.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace blanket sed-based discriminator removal with a targeted Python
post-processing script that only removes discriminator='type' from unions
with conflicting type values:

- Item: InputMessage + OutputMessage (both type='message')
- InputItem: EasyInputMessage + InputMessage + OutputMessage (all type='message')

Other unions (ToolModel, AnnotationModel, ItemResource, RealtimeClientEvent,
RealtimeServerEvent, ResponseStreamEvent, etc.) retain their discriminators
for better Pydantic validation performance.

Also fix .black.toml to use force-exclude instead of exclude, which is
required when files are passed explicitly (as pre-commit does).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The model field is overridden from ModelIdsShared to str to support
a broader set of model IDs than the OpenAI enum.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@dmchoiboi dmchoiboi enabled auto-merge (squash) January 10, 2026 01:13
@dmchoiboi dmchoiboi merged commit ae53ee6 into main Jan 10, 2026
7 checks passed
@dmchoiboi dmchoiboi deleted the fix/datamodel-codegen-rootmodel-defaults branch January 10, 2026 01:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants