mirror of https://github.com/tiangolo/fastapi.git
🐛 Fix optional sequence handling in `serialize sequence value` with Pydantic V2 (#14297)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
This commit is contained in:
parent
3c54a8f07b
commit
0f613d9051
|
|
@ -371,6 +371,13 @@ def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
|
||||||
|
|
||||||
def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
|
def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
|
||||||
origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation
|
origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation
|
||||||
|
if origin_type is Union: # Handle optional sequences
|
||||||
|
union_args = get_args(field.field_info.annotation)
|
||||||
|
for union_arg in union_args:
|
||||||
|
if union_arg is type(None):
|
||||||
|
continue
|
||||||
|
origin_type = get_origin(union_arg) or union_arg
|
||||||
|
break
|
||||||
assert issubclass(origin_type, shared.sequence_types) # type: ignore[arg-type]
|
assert issubclass(origin_type, shared.sequence_types) # type: ignore[arg-type]
|
||||||
return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return]
|
return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,30 @@ def test_is_uploadfile_sequence_annotation():
|
||||||
assert is_uploadfile_sequence_annotation(Union[List[str], List[UploadFile]])
|
assert is_uploadfile_sequence_annotation(Union[List[str], List[UploadFile]])
|
||||||
|
|
||||||
|
|
||||||
|
@needs_pydanticv2
|
||||||
|
def test_serialize_sequence_value_with_optional_list():
|
||||||
|
"""Test that serialize_sequence_value handles optional lists correctly."""
|
||||||
|
from fastapi._compat import v2
|
||||||
|
|
||||||
|
field_info = FieldInfo(annotation=Union[List[str], None])
|
||||||
|
field = v2.ModelField(name="items", field_info=field_info)
|
||||||
|
result = v2.serialize_sequence_value(field=field, value=["a", "b", "c"])
|
||||||
|
assert result == ["a", "b", "c"]
|
||||||
|
assert isinstance(result, list)
|
||||||
|
|
||||||
|
|
||||||
|
@needs_pydanticv2
|
||||||
|
def test_serialize_sequence_value_with_none_first_in_union():
|
||||||
|
"""Test that serialize_sequence_value handles Union[None, List[...]] correctly."""
|
||||||
|
from fastapi._compat import v2
|
||||||
|
|
||||||
|
field_info = FieldInfo(annotation=Union[None, List[str]])
|
||||||
|
field = v2.ModelField(name="items", field_info=field_info)
|
||||||
|
result = v2.serialize_sequence_value(field=field, value=["x", "y"])
|
||||||
|
assert result == ["x", "y"]
|
||||||
|
assert isinstance(result, list)
|
||||||
|
|
||||||
|
|
||||||
@needs_py_lt_314
|
@needs_py_lt_314
|
||||||
def test_is_pv1_scalar_field():
|
def test_is_pv1_scalar_field():
|
||||||
from fastapi._compat import v1
|
from fastapi._compat import v1
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from fastapi import FastAPI, File
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/files")
|
||||||
|
async def upload_files(files: Optional[List[bytes]] = File(None)):
|
||||||
|
if files is None:
|
||||||
|
return {"files_count": 0}
|
||||||
|
return {"files_count": len(files), "sizes": [len(f) for f in files]}
|
||||||
|
|
||||||
|
|
||||||
|
def test_optional_bytes_list():
|
||||||
|
client = TestClient(app)
|
||||||
|
response = client.post(
|
||||||
|
"/files",
|
||||||
|
files=[("files", b"content1"), ("files", b"content2")],
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {"files_count": 2, "sizes": [8, 8]}
|
||||||
|
|
||||||
|
|
||||||
|
def test_optional_bytes_list_no_files():
|
||||||
|
client = TestClient(app)
|
||||||
|
response = client.post("/files")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {"files_count": 0}
|
||||||
Loading…
Reference in New Issue