Skip to content

Conversation

stickM4N
Copy link

After pydantic update to support Python 3.14, different declaration syntax were affected by the changed.
I check and on pydantic side there is not changes on the resulting FieldInfo structure, at least at first glance.
In the tests I illustrated the 3 possible declaration syntax for annotations defined by pydantic and supported so far by sqlmodel. Two of them do not work any more with pydantic>=2.12!

@stickM4N

This comment has been minimized.

@stickM4N

This comment was marked as resolved.

@stickM4N
Copy link
Author

solves #1602

@YuriiMotov YuriiMotov changed the title Pydantic 2.12 declaration syntax broken 🐛 Fix support for Annotated fields with Pydantic 2.12+ Oct 20, 2025
@YuriiMotov YuriiMotov added the bug Something isn't working label Oct 20, 2025
Comment on lines 568 to 589
continue

original_field = getattr(v, "_original_assignment", Undefined)
# Get the original sqlmodel FieldInfo, pydantic >=v2.12 changes the model
if isinstance(original_field, FieldInfo):
field = original_field
else:
annotated_field = next(
ann
for c in new_cls.__mro__
if (ann := get_annotations(c.__dict__).get(k)) # type: ignore[arg-type]
)
annotated_field_meta = getattr(
annotated_field, "__metadata__", (())
)
field = next(
(f for f in annotated_field_meta if isinstance(f, FieldInfo)),
v,
) # type: ignore[assignment]
field.annotation = v.annotation
# Guarantee the field has the correct type
col = get_column_from_field(field)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
continue
original_field = getattr(v, "_original_assignment", Undefined)
# Get the original sqlmodel FieldInfo, pydantic >=v2.12 changes the model
if isinstance(original_field, FieldInfo):
field = original_field
else:
annotated_field = next(
ann
for c in new_cls.__mro__
if (ann := get_annotations(c.__dict__).get(k)) # type: ignore[arg-type]
)
annotated_field_meta = getattr(
annotated_field, "__metadata__", (())
)
field = next(
(f for f in annotated_field_meta if isinstance(f, FieldInfo)),
v,
) # type: ignore[assignment]
field.annotation = v.annotation
# Guarantee the field has the correct type
col = get_column_from_field(field)
else:
original_field = getattr(v, "_original_assignment", Undefined)
if isinstance(original_field, FieldInfo):
field = original_field
else:
field = find_field_info(new_cls, k, v)
field.annotation = v.annotation
col = get_column_from_field(field)
setattr(new_cls, k, col)

I think this part will be clearer with if..else instead of continue and with some logic moved to a separate function:

def find_field_info(cls: type, field_name: str, fallback: FieldInfo) -> FieldInfo:
    for c in cls.__mro__:
        annotated = get_annotations(c.__dict__).get(field_name)  # type: ignore[arg-type]
        if annotated:
            for meta in getattr(annotated, "__metadata__", ()):
                if isinstance(meta, FieldInfo):
                    return meta
    return fallback

Copy link
Author

Choose a reason for hiding this comment

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

Yes, it's easier to read, I will apply it

Copy link
Author

Choose a reason for hiding this comment

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

Just upload a change simplifying the logic based on your suggestion, maybe this way is more straight-forward

@stickM4N stickM4N force-pushed the pydantic-2.12-integration branch from 3f6bec5 to eb3cfcc Compare October 20, 2025 20:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants