Created
February 27, 2026 18:49
-
-
Save DanielGibson/05a8b3dc604cddeb09f30cf0aa0b17b2 to your computer and use it in GitHub Desktop.
Reproducer for SQLModel -> JSON -> SQLModel bug
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
| # Minimal reproducer for bugs and inconsistencies around SQLModel->Json->SQLModel roundtrips | |
| # Basically: For SQLModels with table=True, model_validate_json(jstr) is broken, | |
| # and so is model_validate( json.loads(jstr) ), but in slightly different ways. | |
| # | |
| # model_validate_json(jstr) does not restore non-JSON types properly when used with a table=True SQLModel | |
| # For example, UUIDs remain strings. | |
| # | |
| # model_validate( json.loads(jstr) ) *does* restore those types properly when used directly on | |
| # an SQLModel table=True type, but when that SQLModel is a member of another type it's broken in | |
| # the same way as the former case (e.g. UUIDs remain strings) | |
| # | |
| # Weirdly, for SQLModels with table=False everything seems to work. | |
| import json | |
| import traceback | |
| import warnings | |
| from uuid import uuid4, UUID | |
| from pydantic import BaseModel | |
| from sqlmodel import SQLModel, Field as SQLField | |
| class InnerModelSQLNoTable(SQLModel, table=False): | |
| id: UUID = SQLField(default_factory=uuid4, primary_key=True) | |
| class InnerModelSQL(InnerModelSQLNoTable, table=True): | |
| pass | |
| class OuterModel(BaseModel): | |
| im: InnerModelSQL | |
| class OuterModelNoTable(BaseModel): | |
| im: InnerModelSQLNoTable | |
| def repro_impl(obj, use_model_validate_json): | |
| cls_model = type(obj) | |
| m = "model_validate_json(j)" if use_model_validate_json else "model_validate(json.loads(j))" | |
| print(f"\nChecking for {cls_model.__name__}, creating copy from json with {m}") | |
| j = obj.model_dump_json() | |
| if use_model_validate_json: | |
| obj2 = cls_model.model_validate_json(j) | |
| else: | |
| obj2 = cls_model.model_validate(json.loads(j)) | |
| im = obj2 if cls_model.__name__.startswith("Inner") else obj2.im | |
| if isinstance(im.id, UUID): | |
| print("... ok") | |
| else: | |
| print(f"!!! ERROR: InnerModel*.id is not a UUID but {type(im.id)}") | |
| try: | |
| obj2.model_dump_json() | |
| except: | |
| print("obj2.model_dump_json() failed with") | |
| traceback.print_exc() | |
| def repro(with_table): | |
| if with_table: | |
| print("\n### Testing with an InnerModel derived from SQLModel with table=True") | |
| im = InnerModelSQL() | |
| om = OuterModel(im=im) | |
| else: | |
| print("\n### Testing with an InnerModel derived from SQLModel with table=False") | |
| im = InnerModelSQLNoTable() | |
| om = OuterModelNoTable(im=im) | |
| repro_impl(im, True) | |
| repro_impl(om, True) | |
| repro_impl(im, False) | |
| repro_impl(om, False) | |
| if __name__ == "__main__": | |
| #warnings.simplefilter("error") # turn UserWarnings into exceptions | |
| repro(False) | |
| repro(True) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment