Skip to content

Commit 23aff38

Browse files
committed
Fix instance of related object added to session on validation
Previously, when validating instance when a session was open and the model instance had a related object a new instance of this related object was created and added to the session.
1 parent a20a3a8 commit 23aff38

File tree

2 files changed

+72
-3
lines changed

2 files changed

+72
-3
lines changed

sqlmodel/_compat.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ def sqlmodel_validate(
335335
for key in new_obj.__sqlmodel_relationships__:
336336
value = getattr(use_obj, key, Undefined)
337337
if value is not Undefined:
338-
setattr(new_obj, key, value)
338+
new_obj.__dict__[key] = value
339339
return new_obj
340340

341341
def sqlmodel_init(*, self: "SQLModel", data: Dict[str, Any]) -> None:

tests/test_validation.py

+71-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
from typing import Optional
1+
from typing import List, Optional
22

33
import pytest
44
from pydantic.error_wrappers import ValidationError
5-
from sqlmodel import SQLModel
5+
from sqlmodel import Session, SQLModel, create_engine
6+
from sqlmodel.main import Field, Relationship
67

78
from .conftest import needs_pydanticv1, needs_pydanticv2
89

@@ -63,3 +64,71 @@ def reject_none(cls, v):
6364

6465
with pytest.raises(ValidationError):
6566
Hero.model_validate({"name": None, "age": 25})
67+
68+
69+
@needs_pydanticv1
70+
def test_validation_related_object_not_in_session_pydantic_v1(clear_sqlmodel):
71+
class Team(SQLModel, table=True):
72+
id: Optional[int] = Field(default=None, primary_key=True)
73+
name: str
74+
heroes: List["Hero"] = Relationship(back_populates="team")
75+
76+
class Hero(SQLModel, table=True):
77+
id: Optional[int] = Field(default=None, primary_key=True)
78+
name: str
79+
80+
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
81+
team: Optional[Team] = Relationship(back_populates="heroes")
82+
83+
engine = create_engine("sqlite://")
84+
SQLModel.metadata.create_all(engine)
85+
team = Team(name="team")
86+
hero = Hero(name="hero", team=team)
87+
with Session(engine) as session:
88+
session.add(team)
89+
session.add(hero)
90+
session.commit()
91+
92+
with Session(engine) as session:
93+
hero = session.get(Hero, 1)
94+
assert not session.dirty
95+
assert not session.new
96+
97+
Hero.validate(hero)
98+
99+
assert not session.dirty
100+
assert not session.new
101+
102+
103+
@needs_pydanticv2
104+
def test_validation_related_object_not_in_session_pydantic_v2(clear_sqlmodel):
105+
class Team(SQLModel, table=True):
106+
id: Optional[int] = Field(default=None, primary_key=True)
107+
name: str
108+
heroes: List["Hero"] = Relationship(back_populates="team")
109+
110+
class Hero(SQLModel, table=True):
111+
id: Optional[int] = Field(default=None, primary_key=True)
112+
name: str
113+
114+
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
115+
team: Optional[Team] = Relationship(back_populates="heroes")
116+
117+
engine = create_engine("sqlite://")
118+
SQLModel.metadata.create_all(engine)
119+
team = Team(name="team")
120+
hero = Hero(name="hero", team=team)
121+
with Session(engine) as session:
122+
session.add(team)
123+
session.add(hero)
124+
session.commit()
125+
126+
with Session(engine) as session:
127+
hero = session.get(Hero, 1)
128+
assert not session.dirty
129+
assert not session.new
130+
131+
Hero.model_validate(hero)
132+
133+
assert not session.dirty
134+
assert not session.new

0 commit comments

Comments
 (0)