Created
May 5, 2026 18:29
-
-
Save Graeme22/71d518c5ea2815659b92e62e2e2448f8 to your computer and use it in GitHub Desktop.
SQLModel class that actually validates
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from typing import Any | |
| from pydantic_core import PydanticUndefined as Undefined | |
| from sqlmodel import Field | |
| from sqlmodel import SQLModel as _SQLModel | |
| from sqlmodel._compat import finish_init, is_table_model_class | |
| class SQLModel(_SQLModel): | |
| def __init__(__pydantic_self__, **data: Any) -> None: | |
| # Uses something other than `self` the first arg to allow "self" as a | |
| # settable attribute | |
| # SQLAlchemy does very dark black magic and modifies the __init__ method in | |
| # sqlalchemy.orm.instrumentation._generate_init() | |
| # so, to make SQLAlchemy work, it's needed to explicitly call __init__ to | |
| # trigger all the SQLAlchemy logic, it doesn't work using cls.__new__, setting | |
| # attributes obj.__dict__, etc. The __init__ method has to be called. But | |
| # there are cases where calling all the default logic is not ideal, e.g. | |
| # when calling Model.model_validate(), as the validation is done outside | |
| # of instance creation. | |
| # At the same time, __init__ is what users would normally call, by creating | |
| # a new instance, which should have validation and all the default logic. | |
| # So, to be able to set up the internal SQLAlchemy logic alone without | |
| # executing the rest, and support things like Model.model_validate(), we | |
| # use a contextvar to know if we should execute everything. | |
| if finish_init.get(): | |
| old_dict = __pydantic_self__.__dict__.copy() | |
| __pydantic_self__.__pydantic_validator__.validate_python( | |
| data, | |
| self_instance=__pydantic_self__, | |
| ) | |
| if not is_table_model_class(__pydantic_self__.__class__): | |
| object.__setattr__( | |
| __pydantic_self__, | |
| "__dict__", | |
| {**old_dict, **__pydantic_self__.__dict__}, | |
| ) | |
| else: | |
| fields_set = __pydantic_self__.__pydantic_fields_set__.copy() | |
| for key, value in {**old_dict, **__pydantic_self__.__dict__}.items(): | |
| setattr(__pydantic_self__, key, value) | |
| object.__setattr__( | |
| __pydantic_self__, "__pydantic_fields_set__", fields_set | |
| ) | |
| for key in __pydantic_self__.__sqlmodel_relationships__: | |
| value = data.get(key, Undefined) | |
| if value is not Undefined: | |
| setattr(__pydantic_self__, key, value) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage: