From 3d90df0718ae16991dad29941bd61186ff9f0097 Mon Sep 17 00:00:00 2001 From: dotX12 Date: Sun, 23 Jul 2023 01:58:33 +0300 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=90=9B=20Fix=20parsing=20values=20in?= =?UTF-8?q?=20FormData=20for=20Union/Optional=20in=20list,=20set,=20tuple?= =?UTF-8?q?=20and=20more=20for=20PydanticV2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/fastapi/_compat.py b/fastapi/_compat.py index 2233fe33c..2aef71d43 100644 --- a/fastapi/_compat.py +++ b/fastapi/_compat.py @@ -223,7 +223,21 @@ if PYDANTIC_V2: ) and not isinstance(field.field_info, params.Body) def is_sequence_field(field: ModelField) -> bool: - return field_annotation_is_sequence(field.field_info.annotation) + return field_annotation_is_sequence( + field.field_info.annotation + ) or field_annotation_is_optional_sequence(field.field_info.annotation) + + def field_annotation_is_optional_sequence( + annotation: Union[Type[Any], None] + ) -> bool: + origin = get_origin(annotation) + if origin is Union: + args = get_args(annotation) + first_argument = args[0] + if hasattr(first_argument, "__origin__"): + if first_argument.__origin__ in sequence_types: + return True + return False def is_scalar_sequence_field(field: ModelField) -> bool: return field_annotation_is_scalar_sequence(field.field_info.annotation) From 032c5077da185893b3bd830e8a75834afe01a5ea Mon Sep 17 00:00:00 2001 From: dotX12 Date: Sun, 23 Jul 2023 02:01:11 +0300 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=85=20A=20test=20`test=5Fmodel=5Fopti?= =?UTF-8?q?onal=5Funion=5Fv2`=20has=20been=20added=20to=20coverage=20the?= =?UTF-8?q?=20is=5Fsequence=5Ffield=20function,=20which=20is=20involved=20?= =?UTF-8?q?in=20parsing=20data=20from=20FormData.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_compat.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/test_compat.py b/tests/test_compat.py index 47160ee76..b35d108c4 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -1,4 +1,4 @@ -from typing import List, Union +from typing import FrozenSet, List, Optional, Set, Union from fastapi import FastAPI, UploadFile from fastapi._compat import ( @@ -6,6 +6,7 @@ from fastapi._compat import ( Undefined, _get_model_config, is_bytes_sequence_annotation, + is_sequence_field, is_uploadfile_sequence_annotation, ) from fastapi.testclient import TestClient @@ -91,3 +92,24 @@ def test_is_uploadfile_sequence_annotation(): # and other types, but I'm not even sure it's a good idea to support it as a first # class "feature" assert is_uploadfile_sequence_annotation(Union[List[str], List[UploadFile]]) + + +@needs_pydanticv2 +def test_model_optional_union_v2(): + # For coverage + types = [ + Optional[List[str]], + Union[List[int], List[float]], + Optional[Set[int]], + Union[Set[int], Set[float]], + Optional[FrozenSet[int]], + Union[List[int], None], + ] + for annotation in types: + field_info = FieldInfo(annotation=annotation) + field = ModelField(name="foo", field_info=field_info) + assert is_sequence_field(field) is True + + field_info_str = FieldInfo(annotation=str) + field_str = ModelField(name="foo", field_info=field_info_str) + assert is_sequence_field(field_str) is False From 05d7e7ca90b34e22b3fa6b8f85d36d2f57bd033a Mon Sep 17 00:00:00 2001 From: dotX12 Date: Sat, 6 Jan 2024 23:41:49 +0300 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=90=9B=20Fix=20validation=20error=20U?= =?UTF-8?q?nion[None,=20List[str]]=20and=20Union[List[str],=20None]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fastapi/_compat.py b/fastapi/_compat.py index 2aef71d43..01b1bcfb9 100644 --- a/fastapi/_compat.py +++ b/fastapi/_compat.py @@ -233,10 +233,10 @@ if PYDANTIC_V2: origin = get_origin(annotation) if origin is Union: args = get_args(annotation) - first_argument = args[0] - if hasattr(first_argument, "__origin__"): - if first_argument.__origin__ in sequence_types: - return True + for arg in args: + if hasattr(arg, "__origin__"): + if arg.__origin__ in sequence_types: + return True return False def is_scalar_sequence_field(field: ModelField) -> bool: From 968da3cfab38722849b3843d3c24883f9299adb4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 05:25:51 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20for?= =?UTF-8?q?mat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi/_compat.py b/fastapi/_compat.py index 7293a3560..6e9a6f0ed 100644 --- a/fastapi/_compat.py +++ b/fastapi/_compat.py @@ -251,7 +251,7 @@ if PYDANTIC_V2: ) or field_annotation_is_optional_sequence(field.field_info.annotation) def field_annotation_is_optional_sequence( - annotation: Union[Type[Any], None] + annotation: Union[Type[Any], None], ) -> bool: origin = get_origin(annotation) if origin is Union: