pgjones / quart-schema

Quart-Schema is a Quart extension that provides schema validation and auto-generated API documentation.
MIT License
76 stars 24 forks source link

Issue with file upload request schema validation #89

Open nkma-ml opened 2 weeks ago

nkma-ml commented 2 weeks ago

Hello.

I'm trying to add schema validation to an upload file endpoint. It looks as follows:

from pydantic import BaseModel
from quart_schema import DataSource, validate_request
from quart_schema.pydantic import File

class Upload(BaseModel):
    file: File 

@file_content_bp.route("/upload_file", methods=["POST"])
@validate_request(Upload, source=DataSource.FORM_MULTIPART)
async def upload_files(file_upload: Upload):
    file_content = file_upload.file.read()
    logging.info(file_content)
    return {"status":"success"}

When I try to invoke it using the following request:

curl --location 'http://localhost:50505/upload_file' \
--header 'Content-Type: multipart/form-data' \
--form 'file=@"<PATH TO LOCAL FILE>"'

I get the following error:

Traceback (most recent call last):
  File "/localpath/lib/python3.11/site-packages/quart/app.py", line 1403, in handle_request
    return await self.full_dispatch_request(request_context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "localpath/lib/python3.11/site-packages/quart/app.py", line 1441, in full_dispatch_request
    result = await self.handle_user_exception(error)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "localpath/lib/python3.11/site-packages/quart/app.py", line 1029, in handle_user_exception
    raise error
  File "/localpath/lib/python3.11/site-packages/quart/app.py", line 1439, in full_dispatch_request
    result = await self.dispatch_request(request_context)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/localpath/lib/python3.11/site-packages/quart/app.py", line 1535, in dispatch_request
    return await self.ensure_async(handler)(**request_.view_args)  # type: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "localpath/lib/python3.11/site-packages/quart_schema/validation.py", line 170, in wrapper
    return await current_app.ensure_async(func)(*args, data=model, **kwargs)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: upload_files() got an unexpected keyword argument 'data'

Seems like it could be a bug, or am I missing something. The package versions are: quart==0.19.6 quart-schema[pydantic]==0.20.0

Hopefully it's not me having a brainfart.

BR, Niels

ziaxgit commented 2 weeks ago

Hey, I had a similar type error which I fixed by using the word 'data' as the function parameter name. I'm a complete noob so this might be a long shot but it's worth trying.

Replace

async def upload_files(file_upload: Upload):

With

async def upload_files(data: Upload):
nkma-ml commented 2 weeks ago

Hey, I had a similar type error which I fixed by using the word 'data' as the function parameter name. I'm a complete noob so this might be a long shot but it's worth trying.

Replace

async def upload_files(file_upload: Upload):

With

async def upload_files(data: Upload):

That is not it, unfortuanetly. It's also just a parameter name, so would really suprise me.

Beetroit commented 1 week ago

I see what the problem is. When using a validation decorator like @validate_request or @validate_querystring, quart-schema returns specific arguments to your route function, (data for validate_request and query_args for validate_querysting). All you have to do it annotate your route function with the appropriate arg and type and you're good to go.

from pydantic import BaseModel
from quart_schema import DataSource, validate_request
from quart_schema.pydantic import File

class Upload(BaseModel):
    file: File 

@file_content_bp.route("/upload_file", methods=["POST"])
@validate_request(Upload, source=DataSource.FORM_MULTIPART)
async def upload_files(data: Upload):
    file = data.file

    file_content = file.stream.read()
    logging.info(file_content)

    # save the file
    file.save(file.name)

    return {"status":"success"}

Your code can then be rewritten as above.