Skip to content

table=True models bypass Pydantic validation entirely on __init__, accepting invalid data without error #1837

@mahdirajaee

Description

@mahdirajaee

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 validation

Reproduction

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions