← eval7_submission_v1n

fastapi_14430

resolved RESOLVED PASS · 9 tool calls · 17 s · fastapi/fastapi

Task input

🐛 Fix optional sequence handling with new union syntax from Python 3.10

https://github.com/fastapi/fastapi/pull/14297 is only taking one union type into account. I can only recommend using https://github.com/pydantic/typing-inspection for these kind of checks (see https://typing-inspection.pydantic.dev/latest/usage/ for motivation).

Tool calls (10)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"Union\" --include=*.py fastapi | grep \"sequence\" | head -20"
}
{"status": "ok", "stdout": "fastapi/_compat/shared.py:77:def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:\nfastapi/_compat/shared.py:83:
full
{"status": "ok", "stdout": "fastapi/_compat/shared.py:77:def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:\nfastapi/_compat/shared.py:83:def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:\nfastapi/_compat/shared.py:130:def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool:\nfastapi/_compat/v2.py:384:    if origin_type is Union:  # Handle optional sequences\n", "stderr": "", "exit_code": 0}
2run_command
{
 "command": "cat fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "import re\nimport warnings\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass\nfrom enum import Enum\nfrom typing i
full
{"status": "ok", "stdout": "import re\nimport warnings\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass\nfrom enum import Enum\nfrom typing import (\n    Any,\n    Dict,\n    List,\n    Sequence,\n    Set,\n    Tuple,\n    Type,\n    Union,\n    cast,\n)\n\nfrom fastapi._compat import may_v1, shared\nfrom fastapi.openapi.constants import REF_TEMPLATE\nfrom fastapi.types import IncEx, ModelNameMap\nfrom pydantic import BaseModel, TypeAdapter, create_model\nfrom pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError\nfrom pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation\nfrom pydantic import ValidationError as ValidationError\nfrom pydantic._internal._schema_generation_shared import (  # type: ignore[attr-defined]\n    GetJsonSchemaHandler as GetJsonSchemaHandler,\n)\nfrom pydantic._internal._typing_extra import eval_type_lenient\nfrom pydantic._internal._utils import lenient_issubclass as lenient_issubclass\nfrom pydantic.fields import FieldInfo as FieldInfo\nfrom pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema\nfrom pydantic.json_schema import JsonSchemaValue as JsonSchemaValue\nfrom pydantic_core import CoreSchema as CoreSchema\nfrom pydantic_core import PydanticUndefined, PydanticUndefinedType\nfrom pydantic_core import Url as Url\nfrom typing_extensions import Annotated, Literal, get_args, get_origin\n\ntry:\n    from pydantic_core.core_schema import (\n        with_info_plain_validator_function as with_info_plain_validator_function,\n    )\nexcept ImportError:  # pragma: no cover\n    from pydantic_core.core_schema import (\n        general_plain_validator_function as with_info_plain_validator_function,  # noqa: F401\n    )\n\nRequiredParam = PydanticUndefined\nUndefined = PydanticUndefined\nUndefinedType = PydanticUndefinedType\nevaluate_forwardref = eval_type_lenient\nValidator = Any\n\n\nclass BaseConfig:\n    pass\n\n\nclass ErrorWrapper(Exception):\n    pass\n\n\n@data
3run_command
{
 "command": "grep -n \"if origin_type is Union:\" fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "384: if origin_type is Union: # Handle optional sequences\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "384:    if origin_type is Union:  # Handle optional sequences\n", "stderr": "", "exit_code": 0}
4run_command
{
 "command": "sed -n '370,400p' fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": " return shared.is_bytes_sequence_annotation(field.type_)\n\n\ndef copy_field_info(*, field_info: FieldInfo, annotation: Any) -> Fiel
full
{"status": "ok", "stdout": "    return shared.is_bytes_sequence_annotation(field.type_)\n\n\ndef copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:\n    cls = type(field_info)\n    merged_field_info = cls.from_annotation(annotation)\n    new_field_info = copy(field_info)\n    new_field_info.metadata = merged_field_info.metadata\n    new_field_info.annotation = merged_field_info.annotation\n    return new_field_info\n\n\ndef serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:\n    origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation\n    if origin_type is Union:  # Handle optional sequences\n        union_args = get_args(field.field_info.annotation)\n        for union_arg in union_args:\n            if union_arg is type(None):\n                continue\n            origin_type = get_origin(union_arg) or union_arg\n            break\n    assert issubclass(origin_type, shared.sequence_types)  # type: ignore[arg-type]\n    return shared.sequence_annotation_to_type[origin_type](value)  # type: ignore[no-any-return]\n\n\ndef get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]:\n    error = ValidationError.from_exception_data(\n        \"Field required\", [{\"type\": \"missing\", \"loc\": loc, \"input\": {}}]\n    ).errors(include_url=False)[0]\n    error[\"input\"] = None\n    return error  # type: ignore[return-value]\n", "stderr": "", "exit_code": 0}
5run_command
{
 "command": "cat fastapi/_compat/shared.py"
}
{"status": "ok", "stdout": "import sys\nimport types\nimport typing\nfrom collections import deque\nfrom dataclasses import is_dataclass\nfrom typing import (\n
full
{"status": "ok", "stdout": "import sys\nimport types\nimport typing\nfrom collections import deque\nfrom dataclasses import is_dataclass\nfrom typing import (\n    Any,\n    Deque,\n    FrozenSet,\n    List,\n    Mapping,\n    Sequence,\n    Set,\n    Tuple,\n    Type,\n    Union,\n)\n\nfrom fastapi._compat import may_v1\nfrom fastapi.types import UnionType\nfrom pydantic import BaseModel\nfrom pydantic.version import VERSION as PYDANTIC_VERSION\nfrom starlette.datastructures import UploadFile\nfrom typing_extensions import Annotated, get_args, get_origin\n\n# Copy from Pydantic v2, compatible with v1\nif sys.version_info < (3, 9):\n    # Pydantic no longer supports Python 3.8, this might be incorrect, but the code\n    # this is used for is also never reached in this codebase, as it's a copy of\n    # Pydantic's lenient_issubclass, just for compatibility with v1\n    # TODO: remove when dropping support for Python 3.8\n    WithArgsTypes: Tuple[Any, ...] = ()\nelif sys.version_info < (3, 10):\n    WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias)  # type: ignore[attr-defined]\nelse:\n    WithArgsTypes: tuple[Any, ...] = (\n        typing._GenericAlias,  # type: ignore[attr-defined]\n        types.GenericAlias,\n        types.UnionType,\n    )  # pyright: ignore[reportAttributeAccessIssue]\n\nPYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(\".\")[:2])\nPYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2\n\n\nsequence_annotation_to_type = {\n    Sequence: list,\n    List: list,\n    list: list,\n    Tuple: tuple,\n    tuple: tuple,\n    Set: set,\n    set: set,\n    FrozenSet: frozenset,\n    frozenset: frozenset,\n    Deque: deque,\n    deque: deque,\n}\n\nsequence_types = tuple(sequence_annotation_to_type.keys())\n\nUrl: Type[Any]\n\n\n# Copy of Pydantic v2, compatible with v1\ndef lenient_issubclass(\n    cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...], None]\n) -> bool:\n    try:\n        return
6run_command
{
 "command": "grep -n \"UnionType\" fastapi/_compat/shared.py"
}
{"status": "ok", "stdout": "20:from fastapi.types import UnionType\n39: types.UnionType,\n85: if origin is Union or origin is UnionType:\n111: if origin is Unio
full
{"status": "ok", "stdout": "20:from fastapi.types import UnionType\n39:        types.UnionType,\n85:    if origin is Union or origin is UnionType:\n111:    if origin is Union or origin is UnionType:\n132:    if origin is Union or origin is UnionType:\n151:    if origin is Union or origin is UnionType:\n162:    if origin is Union or origin is UnionType:\n171:    if origin is Union or origin is UnionType:\n186:    if origin is Union or origin is UnionType:\n203:    if origin is Union or origin is UnionType:\n", "stderr": "", "exit_code": 0}
7run_command
{
 "command": "cat fastapi/types.py"
}
{"status": "ok", "stdout": "import types\nfrom enum import Enum\nfrom typing import Any, Callable, Dict, Optional, Set, Tuple, Type, TypeVar, Union\n\nfrom pyda
full
{"status": "ok", "stdout": "import types\nfrom enum import Enum\nfrom typing import Any, Callable, Dict, Optional, Set, Tuple, Type, TypeVar, Union\n\nfrom pydantic import BaseModel\n\nDecoratedCallable = TypeVar(\"DecoratedCallable\", bound=Callable[..., Any])\nUnionType = getattr(types, \"UnionType\", Union)\nModelNameMap = Dict[Union[Type[BaseModel], Type[Enum]], str]\nIncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]]\nDependencyCacheKey = Tuple[Optional[Callable[..., Any]], Tuple[str, ...], str]\n", "stderr": "", "exit_code": 0}
8run_command
{
 "command": "sed -n '380,395p' fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "\n\ndef serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:\n origin_type = get_origin(field.field_info.ann
full
{"status": "ok", "stdout": "\n\ndef serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:\n    origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation\n    if origin_type is Union:  # Handle optional sequences\n        union_args = get_args(field.field_info.annotation)\n        for union_arg in union_args:\n            if union_arg is type(None):\n                continue\n            origin_type = get_origin(union_arg) or union_arg\n            break\n    assert issubclass(origin_type, shared.sequence_types)  # type: ignore[arg-type]\n    return shared.sequence_annotation_to_type[origin_type](value)  # type: ignore[no-any-return]\n\n\ndef get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]:\n", "stderr": "", "exit_code": 0}
9edit_file
{
 "filepath": "fastapi/_compat/v2.py",
 "new_string": "    if origin_type is Union or origin_type is shared.UnionType:  # Handle optional sequences",
 "old_string": "    if origin_type is Union:  # Handle optional sequences"
}
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/_compat/v2.py\n+++ b/fastapi/_compat/v2.py\n
full
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/_compat/v2.py\n+++ b/fastapi/_compat/v2.py\n@@ -381,7 +381,7 @@\n \n def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:\n     origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation\n-    if origin_type is Union:  # Handle optional sequences\n+    if origin_type is Union or origin_type is shared.UnionType:  # Handle optional sequences\n         union_args = get_args(field.field_info.annotation)\n         for union_arg in union_args:\n             if union_arg is type(None):\n", "is_truncated": false}
10submit_patch
{}
{"status": "ok", "patch_size": 710, "files_changed": 1}
full
{"status": "ok", "patch_size": 710, "files_changed": 1}

Patch

diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py
index 543a42dd..fec85d9d 100644
--- a/fastapi/_compat/v2.py
+++ b/fastapi/_compat/v2.py
@@ -381,7 +381,7 @@ 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
+    if origin_type is Union or origin_type is shared.UnionType:  # Handle optional sequences
         union_args = get_args(field.field_info.annotation)
         for union_arg in union_args:
             if union_arg is type(None):

Test output

show
.s.........ss                                                            [100%]
=============================== warnings summary ===============================
../../../../../../kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
  /kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
    _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
10 passed, 3 skipped, 1 warning in 0.75s