Skip to content

Commit a80bb85

Browse files
committed
✨ Add PydanticJSONB type for automatic Pydantic model serialization in PostgreSQL
1 parent 3f7681a commit a80bb85

File tree

1 file changed

+27
-0
lines changed

1 file changed

+27
-0
lines changed

sqlmodel/sql/sqltypes.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from typing import Any, cast
22

3+
from pydantic import BaseModel
34
from sqlalchemy import types
45
from sqlalchemy.engine.interfaces import Dialect
6+
from sqlalchemy.dialects.postgresql import JSONB # for Postgres JSONB
57

68

79
class AutoString(types.TypeDecorator): # type: ignore
@@ -14,3 +16,28 @@ def load_dialect_impl(self, dialect: Dialect) -> "types.TypeEngine[Any]":
1416
if impl.length is None and dialect.name == "mysql":
1517
return dialect.type_descriptor(types.String(self.mysql_default_length))
1618
return super().load_dialect_impl(dialect)
19+
20+
21+
class PydanticJSONB(types.TypeDecorator): # type: ignore
22+
"""Custom type to automatically handle Pydantic model serialization."""
23+
24+
impl = JSONB # use JSONB type in Postgres (fallback to JSON for others)
25+
cache_ok = True # allow SQLAlchemy to cache results
26+
27+
def __init__(self, model_class, *args, **kwargs):
28+
super().__init__(*args, **kwargs)
29+
self.model_class = model_class # Pydantic model class to use
30+
31+
def process_bind_param(self, value, dialect):
32+
# Called when storing to DB: convert Pydantic model to a dict (JSON-serializable)
33+
if value is None:
34+
return None
35+
if isinstance(value, BaseModel):
36+
return value.model_dump()
37+
return value # assume it's already a dict
38+
39+
def process_result_value(self, value, dialect):
40+
# Called when loading from DB: convert dict to Pydantic model instance
41+
if value is None:
42+
return None
43+
return self.model_class.parse_obj(value) # instantiate Pydantic model

0 commit comments

Comments
 (0)