Quantcast
Channel: Active questions tagged python - Stack Overflow
Viewing all articles
Browse latest Browse all 13951

FastAPI / pydantic: field_validator not considered when using empty Depends()

$
0
0

I am following Method 2 of this answer to be able to upload multiple files in combination with additional data using fastapi. It is working fine.

After starting to implement the handling of the additional data including validation using pydantic's BaseModel i am facing an issue:

My custom field_validator is working when using the model class directly but it is not working as expected, when using it via FastAPI and Depends().

The key point is that i want to use a python Enum and i want to be able to use the Enum's names in the additional data (query parameters). For this reason i am using a custom validator to only allow names which exist in the enum.

When i initialize the model manually, the validation works as expected:

from enum import Enumfrom pydantic import BaseModel, field_validator, ValidationInfo, ValidationErrorclass VehicleSubSystems(Enum):    A = "A Verbose"    B = "B Verbose"class EvaluationArguments(BaseModel):    vehicle_sub_system: VehicleSubSystems    @field_validator("vehicle_sub_system", mode='before')    @classmethod    def validate_vehicle_sub_system(cls, vehicle_sub_system: str, _info: ValidationInfo) -> VehicleSubSystems:""" Allows using the enum names instead of the values """        try:            return VehicleSubSystems[vehicle_sub_system]        except KeyError:            raise ValueError(f"Can not find vehicle subsystem '{vehicle_sub_system}'. "                             f"Allowed values: {[e.name for e in VehicleSubSystems]}")def test_validation_is_performed():""" Test that the validation is performed """    EvaluationArguments(vehicle_sub_system="A")    try:        EvaluationArguments(vehicle_sub_system="DOES_NOT_EXIST")    except ValidationError:        print("Test passed")    else:        print("Test failed")if __name__ == '__main__':    test_validation_is_performed()    # prints "Test passed" as expected

Combining this with the FastAPI application shows unexpected behavior: The field_validator is not considered. Instead the default behavior of the model class is used.

Server code:

import uvicornfrom typing import Listfrom fastapi import FastAPI, File, Depends, UploadFileapp = FastAPI()def create_args(vehicle_sub_system: str):    return EvaluationArguments(vehicle_sub_system=vehicle_sub_system)@app.post("/process-works")def process_works(files: List[UploadFile] = File(...), eval_args: EvaluationArguments = Depends(create_args)):    return f"Got {len(files)} files and {eval_args}"@app.post("/process-fails")def process_fails(files: List[UploadFile] = File(...), eval_args: EvaluationArguments = Depends()):    return f"Got {len(files)} files and {eval_args}"if __name__ == '__main__':    uvicorn.run(app, host="0.0.0.0", port=8000)

Client code:

import requestsif __name__ == '__main__':    url = 'http://127.0.0.1:8000'    files = [('files', open('d:/temp/a.txt', 'rb')), ('files', open('d:/temp/b.txt', 'rb'))]    params = {"vehicle_sub_system": "A"}    print("Calling process-works")    resp = requests.post(url=f"{url}/process-works", params=params, files=files)    print(resp.json())    print("Calling process-fails")    resp = requests.post(url=f"{url}/process-fails", params=params, files=files)    print(resp.json())    # Output    # Calling process-works    # Got 2 files and vehicle_sub_system=<VehicleSubSystems.A: 'A Verbose'>    # Calling process-fails    # {'detail': [{'type': 'enum', 'loc': ['query', 'vehicle_sub_system'], 'msg': "Input should be 'A Verbose' or 'B Verbose'", 'input': 'A', 'ctx': {'expected': "'A Verbose' or 'B Verbose'"}}]}

The process-works endpoint shows the expected behavior but only when using a separate dependency Depends(create_args) which mimics the direct usage of the model class.

The process-fails endpoint (using Depends()) shows the issue. I would expect that Depends() is just making FastAPI to call the init method of the model class and uses the validation as expected. But somehow it just ignores it.

I could not figure out why, perhaps somebody can explain what happens here and if there is a solution without the workaround?


Viewing all articles
Browse latest Browse all 13951

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>