moth
moth

Reputation: 2339

Using both pydantic models as response throws error

I have a fastapi implementation where the main.py is:

@app.get(
    "/licence_ol/",
    summary="Query Licence info by product id",
    response_description="Successful Query",
    response_model=list[EvergreenOutput],
    tags=[Tags.items]
)
def get_product_id(session: Session = Depends(get_session))-> list[EvergreenOutput]: 

    query = select(Evergreen).where(Evergreen.product_id == Mapping.eim_product_id).limit(200)
    
    return session.exec(query).fetchall()

And the following schemas.py:

class Mapping(SQLModel,table=True):
    id_seq: Optional[int] = Field(default=None,primary_key=True)
    eim_product_id: Optional[int] = None
    mem_product_id: Optional[int] = None
    vendor: Optional[str] = None
    name: Optional[str] = None
    eim_name: Optional[str] = None
    product_alias: Optional[str] = None
    lev: Optional[int] = None
    manufacturer: Optional[str] = None

class MappingOutput(SQLModel):
    eim_product_id: Optional[int] = None
    mem_product_id: Optional[int] = None
    vendor: Optional[str] = None
    name: Optional[str] = None
    eim_name: Optional[str] = None
    product_alias: Optional[str] = None
    lev: Optional[int] = None
    manufacturer: Optional[str] = None


class Evergreen(SQLModel,table=True):
    id_seq: Optional[int] = Field(default=None,primary_key=True)
    product_id: int 
    phase_type: Optional[str] = None
    phase_start: Optional[date] = None
    phase_end: Optional[date] = None
    product_name: Optional[str] = None
    software_product_version_name: Optional[str] = None
    software_product_version_id: Optional[int] = None

class EvergreenOutput(SQLModel):
    product_id: int 
    phase_type: Optional[str] = None
    phase_start: Optional[date] = None
    phase_end: Optional[date] = None
    product_name: Optional[str] = None
    software_product_version_name: Optional[str] = None
    software_product_version_id: Optional[int] = None

The fisrt main.py works, however if I want to use both EvergreenOutput and MappingOutput as the response models, it gives an error:

@app.get(
    "/licence_ol/",
    summary="Query Licence info by product id",
    response_description="Successful Query",
    response_model=list[EvergreenOutput,MappingOutput],
    tags=[Tags.items]
)
def get_product_id(session: Session = Depends(get_session))-> list[EvergreenOutput,MappingOutput]: 

    query = select(Evergreen,Mapping).where(Evergreen.product_id == Mapping.eim_product_id).limit(200)
    
    return session.exec(query).fetchall()

error is:

INFO:     127.0.0.1:53172 - "GET /licence_ol/ HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 404, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 78, in __call__
    return await self.app(scope, receive, send)
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\fastapi\applications.py", line 270, in __call__
    await super().__call__(scope, receive, send)
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\starlette\applications.py", line 124, in __call__
    await self.middleware_stack(scope, receive, send)
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\starlette\middleware\errors.py", line 184, in __call__
    raise exc
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\starlette\middleware\errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\starlette\middleware\exceptions.py", line 75, in __call__
    raise exc
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\starlette\middleware\exceptions.py", line 64, in __call__
    await self.app(scope, receive, sender)
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\fastapi\middleware\asyncexitstack.py", line 21, in __call__
    raise e
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\fastapi\middleware\asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\starlette\routing.py", line 680, in __call__
    await route.handle(scope, receive, send)
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\starlette\routing.py", line 275, in handle
    await self.app(scope, receive, send)
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\starlette\routing.py", line 65, in app
    response = await func(request)
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\fastapi\routing.py", line 253, in app
    content = await serialize_response(
  File "C:\Users\45291029\Documents\venv\ml\lib\site-packages\fastapi\routing.py", line 139, in serialize_response
    raise ValidationError(errors, field.type_)
pydantic.error_wrappers.ValidationError: 200 validation errors for EvergreenOutput
response -> 0 -> product_id
  field required (type=value_error.missing)

The response without providing a type annotation, has the follwoing structure:

[
  {
    "Evergreen": {
      "id_seq": 344,
      "phase_start": "2011-07-22",
      "product_name": "HTTP Server (IHS)",
      "software_product_version_id": 359483,
      "phase_end": "2018-04-30",
      "product_id": 359496,
      "phase_type": "Generally Available",
      "software_product_version_name": "IBM HTTP Server v8.0"
    },
    "Mapping": {
      "eim_product_id": 359496,
      "mem_product_id": 19,
      "name": "HTTP Server (IHS)",
      "product_alias": "IHS",
      "manufacturer": "IBM",
      "id_seq": 0,
      "vendor": "IBM",
      "eim_name": "HTTP Server (IHS)",
      "lev": 100
    }
  },

Upvotes: 0

Views: 631

Answers (2)

rumbarum
rumbarum

Reputation: 983

Response_model is static, it can not be switchable depends on data.

Your Error message

pydantic.error_wrappers.ValidationError: 200 validation errors for EvergreenOutput response -> 0 -> product_id

pointed out 'product_id' is not found on Mappings.

response_model on path operation should be single model or list of one model.

If you want to return seperated 2 dictionary on single response, then merge them on single pydantic Models like below.

class FuncResult(BaseModel):
    Evergreen: EvergreenOutput
    Mapping: MappingOutput

@app.get(
    "/licence_ol/",
    summary="Query Licence info by product id",
    response_description="Successful Query",
    response_model=list[FuncResult],
    tags=[Tags.items]
)
def get_product_id(session: Session = Depends(get_session)) -> list[EvergreenOutput, MappingOutput]:
    query = select(Evergreen, Mapping).where(Evergreen.product_id == Mapping.eim_product_id).limit(200)
    return  session.exec(query).fetchall()

Upvotes: 1

rumbarum
rumbarum

Reputation: 983

It seems like your query is not mapped your pydantic model.

Try these,


class EvergreenOutput(SQLModel):
    product_id: int
    phase_type: Optional[str] = None
    phase_start: Optional[date] = None
    phase_end: Optional[date] = None
    product_name: Optional[str] = None
    software_product_version_name: Optional[str] = None
    software_product_version_id: Optional[int] = None
    # below line is added
    mapping: list[MappingOutput]

...


@app.get(
    "/licence_ol/",
    summary="Query Licence info by product id",
    response_description="Successful Query",
    response_model=list[EvergreenOutput],
    tags=[Tags.items]
)
def get_product_id(session: Session = Depends(get_session)) -> list[EvergreenOutput, MappingOutput]:
    query = select(Evergreen, Mapping).where(Evergreen.product_id == Mapping.eim_product_id).limit(200)

    return session.exec(query).fetchall()

Upvotes: 0

Related Questions