jonra1993/fastapi-alembic-sqlmodel-async

How to pass `models_as_dict=False` into those Response Models?

jymchng opened this issue · 3 comments

Reference: https://stackoverflow.com/questions/76075777/fastapi-returning-a-listreadschema-as-another-schema/76076717#76076717

I have two tables, Category and Project:

from pydantic import BaseModel
from typing import List, Optional
from sqlmodel import SQLModel, Field, Relationship
import inspect
from enum import Enum

class Category(SQLModel, table=True):
    __table_name__ = 'category'
    id: Optional[int] = Field(
        primary_key=True,
        index=True,
        nullable=False,
    )
    category: str
    project_id: Optional[int] = Field(
        default=None, foreign_key='project.id', nullable=False)

class Project(SQLModel, table=True):
    __table_name__ = 'project'
    id: Optional[int] = Field(
        primary_key=True,
        index=True,
        nullable=False,
    )
    categories: List[Category] = Relationship(
        back_populates='project',
        sa_relationship_kwargs={
            "lazy": "selectin",
            'cascade': 'all,delete,delete-orphan',
            "primaryjoin": "category.project_id==project.id",
        })

Definition of CategoryEnum:

class CategoryEnum(str, Enum):
    A = "A"
    B = "B"

I have two read schemas:

class ICategoryRead(BaseModel):
    category: CategoryEnum

class IProjectRead(BaseModel):
    categories: List[ICategoryRead]

Let's say now I have this record:

one_project_read = IProjectRead(
    categories=[
        ICategoryRead(category=CategoryEnum.A)
    ]
)

When I call one_project_read.json() that is what I get:

'{"categories": [{"category": "A"}]}'

I want it to be:

'{"categories": ["A"]}'

I managed to achieve this by doing two things:

  1. Add class Config: ... to IProjectRead:
class IProjectRead(BaseModel):
    categories: List[ICategoryRead]
        
    class Config:
        json_encoders = {                                  <========= ADD THIS
            ICategoryRead: lambda v: v.category,
        }
  1. In my own small testing, pass the keyword argument models_as_dict=False into .json() method:
>>> one_project_read.json(models_as_dict=False)
... '{"categories": ["A"]}'

Finally, my question is, how do I let your awesome create_response function knows that I want models_as_dict=False?

Thank you.

Hello, @jymchng the current create_response is a basic implementation that does not cover this case of responses but I think you can accomplish the same by not using create_response but instead creating the response object itself.

return IGetResponseBase[IProjectRead](data=one_project_read.json(models_as_dict=False))

Hello, @jymchng the current create_response is a basic implementation that does not cover this case of responses but I think you can accomplish the same by not using create_response but instead creating the response object itself.

return IGetResponseBase[IProjectRead](data=one_project_read.json(models_as_dict=False))

What should the annotation of the return value of the GET function be?

def get_project(id: int) -> [what-should-be-this?]:

@jymchng It should be:

def get_project(id: int) -> IGetResponseBase[IProjectRead]: