-
-
Notifications
You must be signed in to change notification settings - Fork 826
Description
Description
When a model has table=True, the sqlmodel_init() function calls sqlmodel_table_construct() instead of self.__pydantic_validator__.validate_python(). This skips ALL validation:
def sqlmodel_init(*, self: "SQLModel", data: dict[str, Any]) -> None:
if not is_table_model_class(self.__class__):
self.__pydantic_validator__.validate_python(data, self_instance=self)
else:
sqlmodel_table_construct(self_instance=self, values=data) # No validationReproduction
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
age: int
hero = Hero(name=12345, age="not a number") # No error! Accepted silently.The same model without table=True correctly raises a ValidationError.
Impact
Users who rely on SQLModel for data integrity — especially the tutorial pattern where a single model is both a FastAPI request model and a table model — silently accept garbage data. The invalid data either:
- Fails later at the database layer with an obscure error
- Gets persisted if the database column type is permissive (SQLite accepts anything)
This breaks the core promise of SQLModel: combining Pydantic validation with SQLAlchemy persistence.
Context
Related to #453. The tension is that SQLAlchemy's instrumentation requires special initialization, but the current solution (skipping ALL validation) is too aggressive. At minimum, this should be documented prominently. Ideally, validation should run first, then values applied through SQLAlchemy's instrumentation layer.