mirror of https://github.com/tiangolo/fastapi.git
🐛 Fix validation error when `File` is declared after `Form` parameter (#11194)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
This commit is contained in:
parent
a84001000e
commit
44fc67632b
|
|
@ -872,20 +872,19 @@ async def _extract_form_body(
|
||||||
received_body: FormData,
|
received_body: FormData,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
values = {}
|
values = {}
|
||||||
first_field = body_fields[0]
|
|
||||||
first_field_info = first_field.field_info
|
|
||||||
|
|
||||||
for field in body_fields:
|
for field in body_fields:
|
||||||
value = _get_multidict_value(field, received_body)
|
value = _get_multidict_value(field, received_body)
|
||||||
|
field_info = field.field_info
|
||||||
if (
|
if (
|
||||||
isinstance(first_field_info, params.File)
|
isinstance(field_info, params.File)
|
||||||
and is_bytes_field(field)
|
and is_bytes_field(field)
|
||||||
and isinstance(value, UploadFile)
|
and isinstance(value, UploadFile)
|
||||||
):
|
):
|
||||||
value = await value.read()
|
value = await value.read()
|
||||||
elif (
|
elif (
|
||||||
is_bytes_sequence_field(field)
|
is_bytes_sequence_field(field)
|
||||||
and isinstance(first_field_info, params.File)
|
and isinstance(field_info, params.File)
|
||||||
and value_is_sequence(value)
|
and value_is_sequence(value)
|
||||||
):
|
):
|
||||||
# For types
|
# For types
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
"""
|
||||||
|
Regression test, Error 422 if Form is declared before File
|
||||||
|
See https://github.com/tiangolo/fastapi/discussions/9116
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from fastapi import FastAPI, File, Form
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/file_before_form")
|
||||||
|
def file_before_form(
|
||||||
|
file: bytes = File(),
|
||||||
|
city: str = Form(),
|
||||||
|
):
|
||||||
|
return {"file_content": file, "city": city}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/file_after_form")
|
||||||
|
def file_after_form(
|
||||||
|
city: str = Form(),
|
||||||
|
file: bytes = File(),
|
||||||
|
):
|
||||||
|
return {"file_content": file, "city": city}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/file_list_before_form")
|
||||||
|
def file_list_before_form(
|
||||||
|
files: Annotated[List[bytes], File()],
|
||||||
|
city: Annotated[str, Form()],
|
||||||
|
):
|
||||||
|
return {"file_contents": files, "city": city}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/file_list_after_form")
|
||||||
|
def file_list_after_form(
|
||||||
|
city: Annotated[str, Form()],
|
||||||
|
files: Annotated[List[bytes], File()],
|
||||||
|
):
|
||||||
|
return {"file_contents": files, "city": city}
|
||||||
|
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def tmp_file_1(tmp_path: Path) -> Path:
|
||||||
|
f = tmp_path / "example1.txt"
|
||||||
|
f.write_text("foo")
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def tmp_file_2(tmp_path: Path) -> Path:
|
||||||
|
f = tmp_path / "example2.txt"
|
||||||
|
f.write_text("bar")
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("endpoint_path", ("/file_before_form", "/file_after_form"))
|
||||||
|
def test_file_form_order(endpoint_path: str, tmp_file_1: Path):
|
||||||
|
response = client.post(
|
||||||
|
url=endpoint_path,
|
||||||
|
data={"city": "Thimphou"},
|
||||||
|
files={"file": (tmp_file_1.name, tmp_file_1.read_bytes())},
|
||||||
|
)
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
assert response.json() == {"file_content": "foo", "city": "Thimphou"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"endpoint_path", ("/file_list_before_form", "/file_list_after_form")
|
||||||
|
)
|
||||||
|
def test_file_list_form_order(endpoint_path: str, tmp_file_1: Path, tmp_file_2: Path):
|
||||||
|
response = client.post(
|
||||||
|
url=endpoint_path,
|
||||||
|
data={"city": "Thimphou"},
|
||||||
|
files=(
|
||||||
|
("files", (tmp_file_1.name, tmp_file_1.read_bytes())),
|
||||||
|
("files", (tmp_file_2.name, tmp_file_2.read_bytes())),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
assert response.json() == {"file_contents": ["foo", "bar"], "city": "Thimphou"}
|
||||||
Loading…
Reference in New Issue