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]:
|
||||
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]
|
||||
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]])
|
||||
|
||||
|
||||
@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
|
||||
def test_is_pv1_scalar_field():
|
||||
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