Repository navigation
Replies: 3 comments
|
This comes from the order inside I ran your example on SQLModel 0.0.48, SQLAlchemy 2.1.5, Pydantic 2.14.1 (Python 3.15.0rc3) and got the same result. Expiring just that attribute in the except ValidationError:
session.expire(record, ["code"])After that, |
|
I know we can use a workaround, as you suggested, with I've already investigated this and identified a potential execution-order issue involving both SQLAlchemy and SQLModel's setters. Looking at the source code, it appears that SQLAlchemy's That's why I raised this discussion: to investigate whether this is a bug or unexpected behavior and find a way to fix the root cause directly in the source code rather than relying on workarounds. |
|
Yes — this is a bug in the contract sense, and your read of the execution order is correct. Here's the full mechanism, why the code is structured that way, and what a root fix would look like. Exact mechanism (still on # Set in SQLAlchemy, before Pydantic to trigger events and updates
if is_table_model_class(self.__class__) and is_instrumented(self, name):
set_attribute(self, name, value)
# Set in Pydantic model to trigger possible validation changes...
if name not in self.__sqlmodel_relationships__:
super().__setattr__(name, value)
That's the contract violation: for Why not just swap the two calls? The code comment ("Set in SQLAlchemy, before Pydantic to trigger events and updates") hints at the reason: pydantic's A safe root fix is validate-or-restore: if is_table_model_class(self.__class__) and is_instrumented(self, name):
old = get_attribute(self, name) # current value, lazy-load safe
set_attribute(self, name, value) # instrumented write, events fire
try:
super().__setattr__(name, value) # pydantic validation
except ValidationError:
set_attribute(self, name, old) # restore via instrumentation
raise
returnBecause the restore also goes through I'd open this as an issue — your repro (rejected assignment + |
Uh oh!
There was an error while loading. Please reload this page.
First Check
Example Code
Description
Assigning a new value to a frozen field on a database-loaded SQLModel raises ValidationError, but still changes the object's value and SQLAlchemy state.
I expect the rejected assignment to leave the original value unchanged.
Instead, after catching the error, the field contains the rejected value. Calling session.commit() persists that value, and loading the record in a new session confirms the change.
This concerns field-level freezing (Field(schema_extra={"frozen": True})) on a table=True model.
Operating System
macOS
Operating System Details
No response
Project Version
Python 3.15.0rc2 · SQLModel 0.0.48 · SQLAlchemy 2.1.5 · Pydantic 2.14.1.
Python Version
python 3.15
Additional Context
No response
All reactions