axon
axon

Reputation: 678

Composite pydantic model

I am writing a FastAPI application, and have the need to essentially merge some properties from two SQLAlchemy model instances into a single Pydantic model for the response, some properties from Object A and some from Object B, returning a "consolidated" object.

class Message:
  id = Column(Integer, primary_key=True)
  subject = Column(Text)
  message = Column(Text)

class FolderLink:
  id = Column(Integer, primary_key=True)
  message_id = Column(Integer, ForeignKey('message.id'))
  folder_id = Column(Integer, ForeignKey('folder.id'))
  is_read = Column(Boolean, nullable=False, default=False)

In application code, I have a Message instance, needing all properties, and the relevant FolderLink instance, from which I need is_read.

My Pydantic schema looks like:

class MessageWithProperties(BaseModel):
  id: int  # from Message
  subject: str  # from Message
  message: str  # from Message
  is_read: bool  # from FolderLink

And in the view code, the only way I can seem to properly pass objects to the Pydantic model is like so:

objs = []
# [...]
for link in links:
  objs.append({**link.message.__dict__, **link.__dict__})

It feels wrong to have to use a dunder method this way. I had experimented with a custom @classmethod constructor on the Pydantic model, but that will not work as I am not directly instantiating them myself, they are instantiated by FastAPI as part of the response handling.

What is the proper way to do this?

Upvotes: 0

Views: 3329

Answers (1)

larsks
larsks

Reputation: 311238

What if instead of mashing all the fields togther you create a top level MessageWithProperties class that is built from two inferior Pydantic models, one for Message and one for FolderLink? Something like:

class BaseSchema(pydantic.BaseModel):
    class Config:
        orm_mode = True


class MessageSchema(BaseSchema):
    id: int  # from Message
    subject: str  # from Message
    message: str  # from Message


class FolderLinkSchema(BaseSchema):
    is_read: bool  # from FolderLink


class CombinedSchema(BaseSchema):
    message: MessageSchema
    folderlink: FolderLinkSchema


objs = []
for link in links:
  objs.append(CombinedSchema(
      message=MessageSchema.from_orm(link.message),
      folderlink=FolderLinkSchema.from_orm(link),
  ))

Now you'll be able to access both the FolderLink and Message attributes from each object in your objs array.

Upvotes: 1

Related Questions