Skip to content

fix: set relationships when model_validate receives a dict - #2073

Open
latent-9 wants to merge 2 commits into
fastapi:mainfrom
latent-9:fix/model-validate-dict-relationships
Open

fix: set relationships when model_validate receives a dict#2073
latent-9 wants to merge 2 commits into
fastapi:mainfrom
latent-9:fix/model-validate-dict-relationships

Conversation

@latent-9

Copy link
Copy Markdown

Summary

sqlmodel_validate (which backs SQLModel.model_validate and the deprecated from_orm/parse_obj) sets relationships with attribute access:

for key in new_obj.__sqlmodel_relationships__:
    value = getattr(use_obj, key, Undefined)
    if value is not Undefined:
        setattr(new_obj, key, value)

But use_obj is a plain dict in the common case: it is the input obj when a dict is passed, or {**obj, **update} when update= is given. getattr(some_dict, "team", Undefined) never reads dict keys, so it always returns Undefined and the relationship is silently dropped, no error.

This makes model_validate inconsistent with both the constructor and model_validate(object):

team = Team(name="Avengers")
Hero(name="IronMan", team=team).team is team                         # True
Hero.model_validate({"name": "Thor", "team": team}).team is team     # False (dropped)
Hero.model_validate(SimpleNamespace(name="Loki", team=team)).team    # works (object -> getattr)
Hero.model_validate({"name": "Hulk"}, update={"team": team}).team    # False (merged dict, dropped)

The sibling sqlmodel_table_construct already handles this correctly with values.get(key, Undefined).

Fix

Read the relationship via dict.get when use_obj is a dict, falling back to getattr otherwise (mirroring sqlmodel_table_construct). This also fixes the update= path.

Test

Added test_validate_dict_sets_relationship: a relationship passed in the dict to model_validate (and via update=) must be set, matching the constructor. It fails before the change and passes after. The existing validation tests only pass scalar fields, so this path was uncovered.

latent-9 and others added 2 commits August 11, 2026 20:10
sqlmodel_validate read each relationship with getattr(use_obj, key), but
use_obj is a dict when a dict is passed to model_validate (or when update
is given, which produces a merged dict). getattr never finds dict keys, so
the relationship was silently dropped, unlike the constructor and
model_validate(object) which set it. Read via dict.get for dict input,
mirroring sqlmodel_table_construct, and add a regression test.
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