Browse Source

fix ISSUE 13533

pull/14212/head
Abhishek Biswas 9 months ago
parent
commit
e201c735aa
  1. 13
      fastapi/dependencies/utils.py
  2. 103
      tests/test_forms_empty_string_default_13533.py

13
fastapi/dependencies/utils.py

@ -899,6 +899,8 @@ async def _extract_form_body(
received_body: FormData,
) -> Dict[str, Any]:
values = {}
# Track which keys are defined as body_fields to avoid re-adding empty strings
body_field_aliases = {field.alias for field in body_fields}
for field in body_fields:
value = _get_multidict_value(field, received_body)
@ -928,10 +930,17 @@ async def _extract_form_body(
for sub_value in value:
tg.start_soon(process_fn, sub_value.read)
value = serialize_sequence_value(field=field, value=results)
if value is not None:
# Don't add empty strings for Form fields - _get_multidict_value already handled them
if value is not None and not (
isinstance(field_info, (params.Form, temp_pydantic_v1_params.Form))
and isinstance(value, str)
and value == ""
):
values[field.alias] = value
# Only add extra fields that are not already defined as body_fields
# This prevents overwriting correctly processed default values
for key, value in received_body.items():
if key not in values:
if key not in body_field_aliases and key not in values:
values[key] = value
return values

103
tests/test_forms_empty_string_default_13533.py

@ -0,0 +1,103 @@
"""
Test for issue #13533: Multiple regressions in the handling of forms & form validation
This test verifies that empty strings in form fields are correctly handled:
- For optional fields with None default, empty string should result in None
- For optional fields with int type, empty string should result in None (not parsing error)
- This applies to both x-www-form-urlencoded and multipart/form-data
"""
from typing import Annotated, Optional
from fastapi import FastAPI, File, Form
from fastapi.testclient import TestClient
# Test app for URL-encoded forms with optional string
app_urlencoded_str = FastAPI()
@app_urlencoded_str.post("/test")
def endpoint_urlencoded_str(
name: Annotated[Optional[str], Form(embed=True)] = None,
):
return {"name": name}
# Test app for URL-encoded forms with optional int
app_urlencoded_int = FastAPI()
@app_urlencoded_int.post("/test")
def endpoint_urlencoded_int(
age: Annotated[Optional[int], Form()] = None,
):
return {"age": age}
# Test app for multipart forms
app_multipart = FastAPI()
@app_multipart.post("/test")
def endpoint_multipart(
file: Annotated[Optional[bytes], File()] = None,
name: Annotated[Optional[str], Form(embed=True)] = None,
):
return {"file_size": len(file) if file else None, "name": name}
def test_urlencoded_empty_string_optional_str():
"""
Regression test for #13533: Empty string in URL-encoded form
with optional str field should use default value (None)
"""
client = TestClient(app_urlencoded_str)
response = client.post("/test", data={"name": ""})
assert response.status_code == 200
assert response.json() == {"name": None}
def test_urlencoded_empty_string_optional_int():
"""
Regression test for #13533: Empty string in URL-encoded form
with optional int field should use default value (None), not cause parsing error
"""
client = TestClient(app_urlencoded_int)
response = client.post("/test", data={"age": ""})
assert response.status_code == 200
assert response.json() == {"age": None}
def test_multipart_empty_string_optional_str():
"""
Regression test for #13533: Empty string in multipart form
with optional str field should use default value (None)
"""
client = TestClient(app_multipart)
response = client.post("/test", data={"name": ""})
assert response.status_code == 200
assert response.json() == {"file_size": None, "name": None}
def test_urlencoded_with_actual_value():
"""Verify that actual values still work correctly"""
client = TestClient(app_urlencoded_str)
response = client.post("/test", data={"name": "John"})
assert response.status_code == 200
assert response.json() == {"name": "John"}
def test_urlencoded_int_with_actual_value():
"""Verify that actual int values still work correctly"""
client = TestClient(app_urlencoded_int)
response = client.post("/test", data={"age": "25"})
assert response.status_code == 200
assert response.json() == {"age": 25}
def test_multipart_with_actual_value():
"""Verify that actual values in multipart forms still work correctly"""
client = TestClient(app_multipart)
response = client.post("/test", data={"name": "John"})
assert response.status_code == 200
assert response.json() == {"file_size": None, "name": "John"}
Loading…
Cancel
Save