quart-schema icon indicating copy to clipboard operation
quart-schema copied to clipboard

400 Bad Request when uploading multiple files+ Form data

Open RahulDas-dev opened this issue 9 months ago • 0 comments

Description When uploading multiple PDF files to the /api/v1/uploads endpoint, the server returns a 400 Bad Request error with the message "The browser (or proxy) sent a request that this server could not understand."

Expected Behavior

The server should accept multiple file uploads with the passwords parameter and return a 201 status code with information about the saved files.

Actual Behavior

The server receives the multipart form data correctly (as seen in the logs) but returns a 400 Bad Request error.

Code

import asyncio
import logging
from typing import List, Self

from pydantic import BaseModel, model_validator
from pydantic.dataclasses import dataclass
from quart import Blueprint, current_app, request
from quart_schema import DataSource, validate_request, validate_response
from quart_schema.pydantic import File

from library.extensions import pdf_loader

bp = Blueprint("uploads", __name__, url_prefix="/uploads")

logger = logging.getLogger(__name__)


class UploadReqst(BaseModel):
    passwords: list[str]   # Tried with List from typings also
    documents: list[File] # Tried with List from typings also


class UploadResponse(BaseModel):
    message: str


class ErrorResponse(BaseModel):
    error: str


# Kept for Deubing purpose only
@bp.before_request
async def log_request():
    if request.method == "POST":
        form_data = await request.form
        logger.debug(f"Received form data: {form_data}")
        # Don't use this in production, just for debugging
        files = await request.files
        logger.debug(f"Received files: {files}")


@bp.route("/", methods=["POST"])
@validate_request(UploadReqst, source=DataSource.FORM_MULTIPART) # Tried with  source=DataSource.MULTIPART 
@validate_response(UploadResponse, 201)
async def post(data: UploadReqst) -> tuple:
    logger.info(f"Total No of documents uploaded {len(data.documents)} ")
    uploads_list = []
    for data_item in data.documents:
        logger.info(f"File name: {data_item.filename}")
        uploads_list.append(pdf_loader.save(data_item, name=data_item.filename))
    saved_list = await asyncio.gather(*uploads_list)
    logger.info(f"Saved list: {len(saved_list)}")
    return UploadResponse(message=f"Saved list: {len(saved_list)}"), 201

Response Body From Swager

<!doctype html>
<html lang=en>
<title>400 Bad Request</title>
<h1>Bad Request</h1>
<p>The browser (or proxy) sent a request that this server could not understand.</p>

Response Header From Swager

content-length: 167 
 content-type: text/html; charset=utf-8 
 date: Mon,28 Apr 2025 16:24:11 GMT 
 server: hypercorn-h11 
 x-env: DEVELOPMENT 
 x-language: en-US 
 x-timezn: UTC 
 x-version: 1.0.0 

Server Logs

2025-04-28 16:24:11,360.360 DEBUG [MainThread] [uploads.py:36] - Received form data: ImmutableMultiDict([('passwords', 'stringzafaf')])
2025-04-28 16:24:11,360.360 DEBUG [MainThread] [uploads.py:39] - Received files: ImmutableMultiDict([('documents', <FileStorage: 'Invoice-Copy-16.pdf' ('application/pdf')>), ('documents', <FileStorage: 'Invoice_TAPL_ER_23_300.pdf' ('application/pdf')>)])
[2025-04-28 16:24:11 +0000] [5552] [INFO] 127.0.0.1:62563 POST /api/v1/uploads/ 1.1 400 167 22594

dependency

>>> import quart
>>> import quart_schema
>>> import pydantic
>>> from importlib.metadata import version
>>>
>>> print(f'quart vesrion {version("quart")}')
quart vesrion 0.20.0
>>> print(f'quart_schema vesrion {version("quart_schema")}')
quart_schema vesrion 0.22.0
>>> print(f'pydantic vesrion {version("pydantic")}')
pydantic vesrion 2.11.1

github link - https://github.com/RahulDas-dev/workflow

How to make this Work ??

RahulDas-dev avatar Apr 28 '25 16:04 RahulDas-dev