简体   繁体   中英

catch errors using pydantic @validator decorator for fastapi input query

I have 2 issues that I cannot tackle so far using docs/goole/stack info:-)

1) How shall I catch ValidationErrors thrown by pydantic model validator decorator when using model as query input for fastapi get query?

context:

I have a pydantic model:

class PointRankReqParams(BaseModel):
    rat: Optional[Literal['2G', '3G', '4G', '5G']] = '3G'
    h3resolution: Optional[int] = 5
    input_srid: Optional[int] = 4326
    bufferdistance: Optional[int] = 5000
    x: float 
    y: float 

    @validator('h3resolution')
    def h3resolution_bins(cls, v):
        if v not in [4,5,6,7,8]:
            raise ValueError('Hexabin size not permitted')
        return v

and FastAPI func that serves GET requests:

@router.get("/point/overall", summary="GET Ranking for given location point")
async def get_ranking_for_location_point(inputparams: PointRankReqParams = Depends(),token: str = Depends(oauth2_scheme)):    
    logger.debug("Here I am")
    return {}

Now when I curl with parameter h3resolution beyond defined range eg http://localhost:9000/rank/point/overall?rat=2G&h3resolution=9&input_srid=4326&bufferdistance=5000&x=21&y=21 I get errors in console that actually makes sense

py-    |   File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__
py-    | pydantic.error_wrappers.ValidationError: 1 validation error for PointRankReqParams
py-    | h3resolution
py-    |   Hexabin size not permitted (type=value_error)

But FastAPI returns HTTP 500 Internal Server Error to end-client. How shall I catch those exceptions that comes from validator?


2. How to use Optional[Literal[...]] construction properly?

I've tried to redefine above example to use in model:

class PointRankReqParams(BaseModel):
...
h3resolution: Optional[Literal[4,5,6,7,8]] = 5

But when I curl GET query eg http://localhost:9000/rank/point/overall?rat=2G&h3resolution=7&input_srid=4326&bufferdistance=5000&x=21&y=21 I receive 422 Unprocessable entry as h3resolution is being interpreted as string not integer:

{
  "detail": [
    {
      "loc": [
        "query",
        "h3resolution"
      ],
      "msg": "unexpected value; permitted: 4, 5, 6, 7, 8",
      "type": "value_error.const",
      "ctx": {
        "given": "7",
        "permitted": [
          4,
          5,
          6,
          7,
          8
        ]
      }
    }
  ]
}

Thanks a lot!

This is really good question, That's been discussed for several years, you can check this github issue . And unfortunately, there's no consensus on how to handle it, documentation just shows a simple use case using plain Python classes. It works with BaseModel and dataclass until you want to have custom validators or something, FastAPI's author says :

Having a single Pydantic model for query parameters could be interpreted as:

http://somedomain.com/?args={"k1":"v1","k2":"v2"}

or

http://somedomain.com/?k1=v1&k2=v2

or many other alternatives...

And we are not even discussing sub-models, that are valid in Pydantic models (and request bodies) but the behavior would be undefined for non-body parameters (query, path, etc).

There's no obvious way to go about how it would be interpreted that works for all the cases (including other people's use cases, future use cases, etc).

So it doesn't really make sense to have it in FastAPI for a custom use case as it's very subjective and dependent on the conventions of the team.

jimcarreer suggested using something like:

class PagingQuery(BaseModel):
    page: conint(ge=1)
    page_size: conint(ge=1, le=250) = 50

    @classmethod
    async def depends(cls, page: int = 1, page_size: int = 50):
        try:
            return cls(page=page, page_size=page_size)
        except ValidationError as e:
            errors = e.errors()
            for error in errors:
                error['loc'] = ['query'] + list(error['loc'])
            raise HTTPException(422, detail=errors)


@app.get("/example", tags=["basic"])
def example(paging: PagingQuery = Depends(PagingQuery.depends)):
    return {"page": paging.page, "page_size": paging.page_size}

But that looks hacky and I would rather stay away from it.

Answering your second question, the query string is just a string (like 'age=20&name=John' ) and key, value pairs are just strings; you have specified Literal type, so there won't be type casting and you'll get an exception.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM