Reputation: 345
class PostingType(str, Enum):
house_seeker = 'House Seeker'
house_sharer = 'House Sharer'
class ResponsePosting(BaseModel):
id: UUID = Field(default_factory=uuid4)
timestamp: date = Field(default_factory=date.today)
title: str = Field(default=...)
description: str = Field(default=...)
class ResponseHouseSeekerPosting(ResponsePosting):
postingType: Literal[PostingType.house_seeker]
class ResponseHouseSharerPosting(ResponsePosting):
postingType: Literal[PostingType.house_sharer]
price: conint(ge=1)
houseSize: HouseSize = Field(default=...)
class ResponseGetPost(BaseModel):
__root__: Union [ResponseHouseSharerPosting, ResponseHouseSeekerPosting] = Field(default=..., discriminator='postingType')
I have an endpoint that will return a list of either ResponseHouseSeekerPosting
or ResponseHouseSharerPosting
, I am trying to figure out what I should write for the response model to make it work
The endpoint looks like this:
@router.get(path='/posts', response_description="Retrieves a list of postings of the user", status_code=200)
async def get_user_posts(post_type: PostingType):
user = users_db_connection.aggregate("Some Aggregate Query that returns the list of posts for the user, this works properly")
async for doc in user:
if post_type == PostingType.house_seeker:
return doc['postings'] # <-- this is type <list> and holds a list of *ResponseHouseSeekerPosting*
else:
return doc['postings'] # <-- this is type <list> and holds a list of *ResponseHouseSharerPosting*
So I have tried many things to get this to work but I can't figure it out, I have checked the following links but to no avail:
All of the above answer my question but only for a single returned element (by using a Discriminated Unions), I have an endpoint that uses ResponseGetPost
to return either models and it works fine, the issue arises when I try to use it for a list
I tried to add a new model like this
class ResponseGetPostList(BaseModel):
list_of_posts : List[ResponseGetPost]
and tried this too
class ResponseGetPostList(BaseModel):
__root__: List[ResponseGetPost]
with the endpoint looking like this:
@router.get(path='/posts', response_description="Retrieves a list of postings of the user", status_code=200, response_model=ResponseGetPostList) <-- the response_model changed
async def get_user_posts(post_type: PostingType):
user = users_db_connection.aggregate("Some Aggregate Query that returns the list of posts for the user, this works properly")
async for doc in user:
if post_type == PostingType.house_seeker:
return doc['postings'] # <-- this is type <list> and holds a list of *ResponseHouseSeekerPosting*
else:
return doc['postings'] # <-- this is type <list> and holds a list of *ResponseHouseSharerPosting*
P.S I was sending a body of a house seeker post But it did not work, and this is the error message that appeared:
File "D:\BilMate\BilMate-Backend\venv\Lib\site-packages\fastapi\routing.py", line 145, in serialize_response
raise ValidationError(errors, field.type_)
pydantic.error_wrappers.ValidationError: 3 validation errors for ResponseGetPost
response -> 0 -> __root__ -> postingType
unexpected value; permitted: <PostingType.house_sharer: 'House Sharer'> (type=value_error.const; given=House Seeker; permitted=(<PostingType.house_sharer: 'House Sharer'>,)) <-- this always becomes the opposite of what I provide in the ```post_type``` variable
response -> 0 -> __root__ -> price
field required (type=value_error.missing)
response -> 0 -> __root__ -> houseSize
field required (type=value_error.missing)
Similar to Attempt 1 but without a new model I simply added it like this
@router.get(path='/posts', response_description="Retrieves a list of postings of the user", status_code=200, response_model=List[ResponseGetPost])
I got the same error as Attempt 1
Tried this endpoint response_model but changed the return value:
@router.get(path='/posts', response_description="Retrieves a list of postings of the user", status_code=200, response_model=List[ResponseGetPost])
async def get_user_posts(post_type: PostingType):
user = users_db_connection.aggregate("Some Aggregate Query that returns the list of posts for the user, this works properly")
async for doc in user:
if post_type == PostingType.house_seeker:
print(doc['postings'])
return ResponseHouseSeekerPosting(doc['postings']) <-- This Changed
else:
return ResponseHouseSharerPosting(doc['postings']) <-- This Changed
Also, once again, it did not work. This is the error message I was met with:
File "pydantic\main.py", line 332, in pydantic.main.BaseModel.__init__
TypeError: __init__() takes exactly 1 positional argument (2 given)
I have an endpoint @router.get('/{post_id}', response_description='retrieves a single post', response_model=ResponseGetPost)
that does the same thing I am trying to achieve here but this endpoint only returns a single element. This endpoint works fine so I tried to replicate it but for a list but I'm stuck and would appreciate any help
Upvotes: 1
Views: 1124
Reputation: 675
you can specify response_model
in the route. I guess it can help.
from typing import Union, List
@app.get(
path='/posts',
response_description="Retrieves a list of postings of the user",
status_code=200,
response_model=Union[List[ResponseHouseSharerPosting], List[ResponseHouseSeekerPosting]]
)
async def get_user_posts(post_type: PostingType):
# rest of the code ...
async for doc in user:
if post_type == PostingType.house_seeker:
return [ResponseHouseSeekerPosting(**posting) for posting in doc['postings']]
else:
return [ResponseHouseSharerPosting(**posting) for posting in doc['postings']]
Upvotes: 2