Repository navigation
Best way to do polymorphism / tagged unions with an object in a field in an SQLModel? #1450
Unanswered
PhilippeGSK
asked this question in
Questions
Replies: 2 comments
This comment was marked as off-topic.
This comment was marked as off-topic.
|
Your approach (flat columns plus a computed from typing import Annotated, Literal, Optional, Union
from pydantic import BaseModel, Field as PField, TypeAdapter, computed_field
from sqlmodel import Field, Relationship, SQLModel
# read shapes
class UserMsg(BaseModel):
type: Literal["user"]
contents: str
class ActionMsg(BaseModel):
type: Literal["action"]
contents: str
character: CharacterPublic
class DialogueMsg(BaseModel):
type: Literal["dialogue"]
contents: str
character: CharacterPublic
MessageData = Annotated[Union[UserMsg, ActionMsg, DialogueMsg], PField(discriminator="type")]
# write shapes: action and dialogue share one model
class UserMsgWrite(BaseModel):
type: Literal["user"]
contents: str
class CharacterMsgWrite(BaseModel):
type: Literal["action", "dialogue"]
contents: str
character_id: int
MessageDataWrite = Annotated[Union[UserMsgWrite, CharacterMsgWrite], PField(discriminator="type")]
_read = TypeAdapter(MessageData)
_write = TypeAdapter(MessageDataWrite)
DATA_COLUMNS = ("type", "contents", "character_id") # the only place column names are listed
class ChannelMessage(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
# ... channel_id / author as before
type: str
contents: Optional[str] = None
character_id: Optional[int] = Field(default=None, foreign_key="character.id")
character: Optional[Character] = Relationship()
@computed_field
@property
def data(self) -> MessageData:
return _read.validate_python(self, from_attributes=True)
def set_data(self, val) -> None:
dumped = _write.validate_python(val).model_dump() # accepts a model or a dict, checks the tag
for col in DATA_COLUMNS:
setattr(self, col, dumped.get(col)) # columns the new type doesn't use become None
class ChannelMessagePublic(SQLModel):
id: int
data: MessageDataI ran it on SQLModel 0.0.48 with SQLite:
What this buys you:
If the types diverge a lot later (many type-specific columns), joined-table inheritance in SQLAlchemy is the next step up. For a handful of shared columns like this, a single table plus discriminated unions is simpler. |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
First Check
Commit to Help
Example Code
Description
I'm making a small chat feature.
I'm trying to create a system where a field (data) in my SQLModel table (ChannelMessage) gets populated differently based on a type string. My current setup is in the code above. It works. I was wondering, though, if there were any cleaner and more automated ways to achieve the same result. I'm kind of new to the world of ORMs and things like that in general so I don't know about all the possibilities.
Operating System
Linux
Operating System Details
Debian GNU/Linux 12 (bookworm) x86_64
SQLModel Version
0.0.24
Python Version
Python 3.13.5
Additional Context
No response
All reactions