Create Pydantic model from Huge SQLModel #1108
Unanswered
mmmcorpsvit
asked this question in
Questions
Replies: 2 comments 1 reply
-
my current solution, but maybe present more official way? I adapt for SQLModel converting from tortoise-orm package from typing import Type, Tuple, Dict, Any, Optional, List
from pydantic import BaseModel, Field, create_model, field_validator
from sqlmodel import SQLModel, Field as SQLField, Relationship
# SQLModel Mixins
class TimestampMixin(SQLModel):
created_at: Optional[str] = SQLField(default=None)
updated_at: Optional[str] = SQLField(default=None)
class IdentifiableMixin(SQLModel):
id: int = SQLField(default=None, primary_key=True)
# Pydantic Mixins
class TimestampMixinPydantic(BaseModel):
created_at: Optional[str] = None
updated_at: Optional[str] = None
@field_validator("created_at", "updated_at", mode="before")
def validate_datetime(cls, v):
# Example validation logic
if v and len(v) != 19: # Example check for datetime format
raise ValueError(f"Invalid datetime format: {v}")
return v
class IdentifiableMixinPydantic(BaseModel):
id: int
# Function to create Pydantic models from SQLModel models
def pydantic_model_creator(
cls: Type[SQLModel],
*,
name: Optional[str] = None,
exclude: Tuple[str, ...] = (),
include: Tuple[str, ...] = (),
computed: Tuple[str, ...] = (),
optional: Tuple[str, ...] = (),
sort_alphabetically: Optional[bool] = None,
exclude_readonly: bool = False,
model_config: Optional[dict] = None,
validators: Optional[Dict[str, Any]] = None,
module: str = __name__,
) -> Type[BaseModel]:
"""
Function to build a Pydantic Model off SQLModel Model.
"""
fqname = cls.__module__ + "." + cls.__qualname__
def get_name() -> str:
is_default = (
exclude == ()
and include == ()
and computed == ()
and sort_alphabetically is None
)
hashval = f"{fqname};{exclude};{include};{computed}:{sort_alphabetically}"
postfix = f":{hashval[:6]}" if not is_default else ""
return fqname + postfix
_name = name or get_name()
# Get SQLModel fields
properties: Dict[str, Any] = {}
annotations = cls.__annotations__
for fname, field_type in annotations.items():
if (include and fname not in include) or fname in exclude:
continue
# Access the field information
# field = cls.__fields__.get(fname)
field = cls.model_fields.get(fname)
if not field:
continue
field_default = field.default
is_optional_field = fname in optional
# Handle relationships
if isinstance(field_type, type) and issubclass(field_type, SQLModel):
submodel = pydantic_model_creator(
field_type,
exclude=tuple(f"{fname}.{ex}" for ex in exclude),
include=tuple(f"{fname}.{inc}" for inc in include),
computed=computed,
sort_alphabetically=sort_alphabetically,
exclude_readonly=exclude_readonly,
model_config=model_config,
validators=validators,
)
properties[fname] = Optional[submodel] if field.default is None else List[submodel]
continue
# Handle computed fields
if fname in computed:
func = lambda: f"Computed {fname}"
properties[fname] = Field(default=func, description=f"Computed field: {fname}")
continue
# Handle regular fields
if field_default is None and is_optional_field:
field_default = None
ptype = field_type
if is_optional_field or field_default is not None:
ptype = Optional[ptype]
if not (exclude_readonly and getattr(field, "readonly", False)):
properties[fname] = (ptype, Field(default=field_default))
if sort_alphabetically:
properties = dict(sorted(properties.items()))
# Create Pydantic model
pconfig = model_config or {}
if "title" not in pconfig:
pconfig["title"] = name or cls.__name__
if "extra" not in pconfig:
pconfig["extra"] = "forbid"
model = create_model(
_name,
__base__=BaseModel,
__module__=module,
**properties,
)
# Apply custom validators if provided
if validators:
for field_name, validator_fn in validators.items():
if hasattr(model, field_name):
setattr(model, field_name, field_validator(field_name, mode="before")(validator_fn))
model.__doc__ = cls.__doc__
return model
# Define related SQLModel classes
class CarType(TimestampMixin, IdentifiableMixin, SQLModel, table=True):
name: str
description: Optional[str]
cars: List["Car"] = Relationship(back_populates="car_type")
class Car(TimestampMixin, IdentifiableMixin, SQLModel, table=True):
make: str
model: str
year: int
color: str
car_type_id: int = SQLField(foreign_key="cartype.id")
car_type: Optional[CarType] = Relationship(back_populates="cars")
# Create Pydantic models from SQLModel
CarPydanticModel = pydantic_model_creator(
Car,
name="CarPydantic",
exclude=("car_type_id",), # Exclude field
include=("make", "model", "year", "color"), # Include fields
computed=("full_name",), # Add computed field
optional=("color",), # Mark field as optional
sort_alphabetically=True,
exclude_readonly=True,
model_config={"title": "Car Model"},
validators={
"year": lambda v: v if v > 1900 else 1900 # Use field_validator
}
)
CarTypePydanticModel = pydantic_model_creator(
CarType,
name="CarTypePydantic",
exclude=("description",), # Exclude field
include=("name",), # Include fields
sort_alphabetically=True,
model_config={"title": "Car Type Model"}
)
# Example usage
car_instance = CarPydanticModel(
id=1,
make="Toyota",
model="Camry",
year=2022,
color="Blue",
car_type=CarTypePydanticModel(id=1, name="Sedan")
)
print(car_instance) |
Beta Was this translation helpful? Give feedback.
0 replies
-
Can't your sqlmodel class be used directly? |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
First Check
Commit to Help
Example Code
Description
I have huge main SQLModel class - Lot, with many nested models (LotSaleTitleType , AuctionSeller ) - this models too have nested model etc...
what a best way to create Pydantic model with include/exclude?
for example i need ResponseModel for FastAPI Endpoint:
how i can declare LotShortSchema (just few fields like id, vin etc...) and LotSchema (without few fields) without copy paste/pain. And need include/exclude fields from nested models. Are possible for example exclude from all nested models field 'slug' if present ?
Operating System
Windows
Operating System Details
No response
SQLModel Version
0.0.22
Python Version
3.11
Additional Context
No response
Beta Was this translation helpful? Give feedback.
All reactions