2

My GET endpoint receives a query parameter that needs to meet the following criteria:

  1. be an int between 0 and 10
  2. be even number

1. is straight forward using Query(gt=0, lt=10). However, it is not quiet clear to me how to extend Query to do extra custom validation such as 2.. The documentation ultimately leads to pydantic. But, my application runs into internal server error when the second validation 2. fails.

Below is a minimal scoped example

from fastapi import FastAPI, Depends, Query
from pydantic import BaseModel, ValidationError, validator

app = FastAPI()

class CommonParams(BaseModel):
    n: int = Query(default=..., gt=0, lt=10)

    @validator('n')
    def validate(cls, v):
        if v%2 != 0:
            raise ValueError("Number is not even :( ")
        return v


@app.get("/")
async def root(common: CommonParams = Depends()):
    return {"n": common.n}

Below are requests that work as expected and ones that break:

# requsts that work as expected
localhost:8000?n=-4
localhost:8000?n=-3
localhost:8000?n=2
localhost:8000?n=8
localhost:8000?n=99

# request that break server
localhost:8000?n=1
localhost:8000?n=3
localhost:8000?n=5
Chris
  • 18,724
  • 6
  • 46
  • 80
ooo
  • 673
  • 3
  • 16
  • Can you clarify "break server"? What error message do you get in the terminal? – M.O. Feb 22 '23 at 23:32
  • You raise a ValueError, while [fastapi raises a RequestValidationError](https://fastapi.tiangolo.com/tutorial/handling-errors/#override-request-validation-exceptions) when validation fails. Try raising a RequestValidationError instead and see if that is handled by the fastapi logic. – Saaru Lindestøkke Feb 22 '23 at 23:35
  • 1
    Does this answer your question? [FastAPI - Pydantic - Value Error Raises Internal Server Error](https://stackoverflow.com/questions/68914523/fastapi-pydantic-value-error-raises-internal-server-error) – Saaru Lindestøkke Feb 22 '23 at 23:39
  • 1
    Thank you for your input. This is not quiet what I am looking for. What I am looking for is behaviour that is similar to the `lt` and 'gt' validators but instead of checking if number is less than, it checks if number is even. For example, if input is 99 (which is not between 0 and 10) my endpoint response is `{"detail":[{"loc":["query","n"],"msg":"ensure this value is greater than 0","type":"value_error.number.not_gt","ctx":{"limit_value":0}}]}` I would like to achieve similar behaviour for checking if input is even. – ooo Feb 23 '23 at 02:33

1 Answers1

1

Option 1

Raise HTTPException directly instead of ValueError, as demonstrated in Option 1 of this answer. Example:

from fastapi import FastAPI, Depends, Query, HTTPException
from pydantic import BaseModel, validator

app = FastAPI()

class CommonParams(BaseModel):
    n: int = Query(default=..., gt=0, lt=10)

    @validator('n')
    def prevent_odd_numbers(cls, v):
        if v % 2 != 0:
            raise HTTPException(status_code=422, detail='Input number is not even')
        return v


@app.get('/')
async def root(common: CommonParams = Depends()):
    return {'n': common.n}

Server Response (when the input number is not even, e.g., n = 1):

# 422 Error: Unprocessable Entity

{
  "detail": "Input number is not even"
}

Option 2

Use a custom exception handler, in order to handle ValueError exceptions, similar to this answer and this answer. Example:

from fastapi import FastAPI, Request, Depends, Query, status
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel, validator

app = FastAPI()

@app.exception_handler(ValueError)
async def validation_exception_handler(request: Request, exc: ValueError):
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder({"detail": exc.errors(),  # optionally, include the pydantic errors
                 "custom msg": {"Your error message"}}),   # optionally, return a custom message
    )
    
class CommonParams(BaseModel):
    n: int = Query(default=..., gt=0, lt=10)

    @validator('n')
    def prevent_odd_numbers(cls, v):
        if v % 2 != 0:
            raise ValueError('Input number is not even')
        return v


@app.get('/')
async def root(common: CommonParams = Depends()):
    return {'n': common.n}

Server Response (when the input number is not even, e.g., n = 1):

# 422 Error: Unprocessable Entity

{
  "detail": [
    {
      "loc": [
        "n"
      ],
      "msg": "Input number is not even",
      "type": "value_error"
    }
  ],
  "custom msg": [
    "Your error message"
  ]
}
Chris
  • 18,724
  • 6
  • 46
  • 80