failed NO_PATCH NO_PATCH UNSUBMITTED empty_patch(budget) · 21 tool calls · 302 s · fastapi/fastapi
🐛 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 | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"Union\" fastapi/fastapi | grep \"types\" | head -20"
} | {"status": "ok", "stdout": "", "stderr": "grep: fastapi/fastapi: No such file or directory\n", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "grep: fastapi/fastapi: No such file or directory\n", "exit_code": 0} |
| 2 | run_command | {
"command": "grep -rn \"Union\" fastapi | grep \"types\" | head -20"
} | {"status": "ok", "stdout": "fastapi/dependencies/utils.py:834: from fastapi.types import UnionType\nfastapi/dependencies/utils.py:838: # Check if it's a Union tfull{"status": "ok", "stdout": "fastapi/dependencies/utils.py:834: from fastapi.types import UnionType\nfastapi/dependencies/utils.py:838: # Check if it's a Union type (covers both typing.Union and types.UnionType in Python 3.10+)\nfastapi/types.py:3:from typing import Any, Callable, Dict, Optional, Set, Tuple, Type, TypeVar, Union\nfastapi/types.py:8:UnionType = getattr(types, \"UnionType\", Union)\nfastapi/types.py:9:ModelNameMap = Dict[Union[Type[BaseModel], Type[Enum]], str]\nfastapi/types.py:10:IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]]\nfastapi/_compat/shared.py:20:from fastapi.types import UnionType\nfastapi/_compat/shared.py:39: types.UnionType,\n", "stderr": "", "exit_code": 0} |
| 3 | run_command | {
"command": "sed -n '820,850p' fastapi/dependencies/utils.py"
} | {"status": "ok", "stdout": " )\n loc = (field_info.in_.value, field.alias)\n v_, errors_ = _validate_value_with_model_field(\n field=field, value=value, values=full{"status": "ok", "stdout": " )\n loc = (field_info.in_.value, field.alias)\n v_, errors_ = _validate_value_with_model_field(\n field=field, value=value, values=values, loc=loc\n )\n if errors_:\n errors.extend(errors_)\n else:\n values[field.name] = v_\n return values, errors\n\n\ndef is_union_of_base_models(field_type: Any) -> bool:\n \"\"\"Check if field type is a Union where all members are BaseModel subclasses.\"\"\"\n from fastapi.types import UnionType\n\n origin = get_origin(field_type)\n\n # Check if it's a Union type (covers both typing.Union and types.UnionType in Python 3.10+)\n if origin is not Union and origin is not UnionType:\n return False\n\n union_args = get_args(field_type)\n\n for arg in union_args:\n if not _is_model_class(arg):\n return False\n\n return True\n\n\n", "stderr": "", "exit_code": 0} |
| 4 | run_command | {
"command": "grep -rn \"UnionType\" fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/dependencies/utils.py:834: from fastapi.types import UnionType\nfastapi/dependencies/utils.py:838: # Check if it's a Union tfull{"status": "ok", "stdout": "fastapi/dependencies/utils.py:834: from fastapi.types import UnionType\nfastapi/dependencies/utils.py:838: # Check if it's a Union type (covers both typing.Union and types.UnionType in Python 3.10+)\nfastapi/dependencies/utils.py:839: if origin is not Union and origin is not UnionType:\nfastapi/types.py:8:UnionType = getattr(types, \"UnionType\", Union)\nfastapi/_compat/shared.py:20:from fastapi.types import UnionType\nfastapi/_compat/shared.py:39: types.UnionType,\nfastapi/_compat/shared.py:85: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:111: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:132: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:151: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:162: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:171: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:186: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:203: if origin is Union or origin is UnionType:\n", "stderr": "", "exit_code": 0} |
| 5 | run_command | {
"command": "sed -n '1,210p' 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 (\nfull{"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 |
| 6 | run_command | {
"command": "sed -n '50,100p' fastapi/_compat/shared.py"
} | {"status": "ok", "stdout": " Tuple: tuple,\n tuple: tuple,\n Set: set,\n set: set,\n FrozenSet: frozenset,\n frozenset: frozenset,\n Deque: deque,\n deque: dequfull{"status": "ok", "stdout": " 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 isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type]\n except TypeError: # pragma: no cover\n if isinstance(cls, WithArgsTypes):\n return False\n raise # pragma: no cover\n\n\ndef _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:\n if lenient_issubclass(annotation, (str, bytes)):\n return False\n return lenient_issubclass(annotation, sequence_types) # type: ignore[arg-type]\n\n\ndef field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool:\n origin = get_origin(annotation)\n if origin is Union or origin is UnionType:\n for arg in get_args(annotation):\n if field_annotation_is_sequence(arg):\n return True\n return False\n return _annotation_is_sequence(annotation) or _annotation_is_sequence(\n get_origin(annotation)\n )\n\n\ndef value_is_sequence(value: Any) -> bool:\n return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) # type: ignore[arg-type]\n\n\ndef _annotation_is_complex(annotation: Union[Type[Any], None]) -> bool:\n return (\n", "stderr": "", "exit_code": 0} |
| 7 | run_command | {
"command": "python3 - <<'PY'\nimport typing\nimport types\nfrom typing_extensions import get_origin, get_args\n\n# Mocking UnionType for Python < 3.10 if necessary, \n# but we are likely on 3.10+ given the issue description.\nUnionType = getattr(types, \"UnionType\", typing.Union)\n\ndef field_annotation_is_sequence(annotation):\n origin = get_origin(annotation)\n if origin is typing.Union or origin is UnionType:\n for arg in get_args(annotation):\n if field_annotation_is_sequence(arg):\n return True\n return False\n \n # Simplified version of _annotation_is_sequence for testing\n # In reality, it uses lenient_issubclass and sequence_types\n if annotation in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return True\n if origin in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return True\n return False\n\n# Test cases\n# 1. Standard Union\nprint(f\"Union[list[int], int]: {field_annotation_is_sequence(typing.Union[typing.List[int], int])}\") # Expected True\n\n# 2. Python 3.10 Union syntax (int | list[int])\ntry:\n # This syntax is only valid in Python 3.10+\n print(f\"int | list[int]: {field_annotation_is_sequence(int | list[int])}\") # Expected True\nexcept TypeError:\n print(\"Python version < 3.10, skipping | syntax\")\n\n# 3. Optional sequence (list[int] | None)\ntry:\n print(f\"list[int] | None: {field_annotation_is_sequence(list[int] | None)}\") # Expected True\nexcept TypeError:\n print(\"Python version < 3.10, skipping | syntax\")\n\n# 4. Optional non-sequence (int | None)\ntry:\n print(f\"int | None: {field_annotation_is_sequence(int | None)}\") # Expected False\nexcept TypeError:\n print(\"Python version < 3.10, skipping | syntax\")\nPY\n"
} | {"status": "ok", "stdout": "Union[list[int], int]: True\nint | list[int]: True\nlist[int] | None: True\nint | None: False\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Union[list[int], int]: True\nint | list[int]: True\nlist[int] | None: True\nint | None: False\n", "stderr": "", "exit_code": 0} |
| 8 | run_command | {
"command": "python3 - <<'PY'\nimport typing\nimport types\nfrom typing_extensions import get_origin, get_args\n\nUnionType = getattr(types, \"UnionType\", typing.Union)\n\ndef field_annotation_is_scalar(annotation):\n # Simplified version of field_annotation_is_scalar\n # It returns True if it's NOT complex.\n # For this test, let's just define what's complex.\n # Complex: BaseModel, Mapping, UploadFile, Sequence, dataclass\n # We'll just mock it.\n if annotation in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return False\n return True\n\ndef field_annotation_is_scalar_sequence(annotation):\n origin = get_origin(annotation)\n if origin is typing.Union or origin is UnionType:\n at_least_one_scalar_sequence = False\n for arg in get_args(annotation):\n if field_annotation_is_scalar_sequence(arg):\n at_least_one_scalar_sequence = True\n continue\n elif not field_annotation_is_scalar(arg):\n return False\n return at_least_one_scalar_sequence\n \n # Simplified check for sequence and all scalar\n if origin in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return all(field_annotation_is_scalar(sub) for sub in get_args(annotation))\n return False\n\n# Test cases\n# 1. Scalar sequence in Union\n# list[int] | int -> True (at least one scalar sequence, others are scalar)\ntry:\n print(f\"list[int] | int: {field_annotation_is_scalar_sequence(list[int] | int)}\") \nexcept TypeError:\n print(\"Python < 3.10\")\n\n# 2. Scalar sequence and non-scalar in Union\n# list[int] | list[list[int]] -> False (one is scalar sequence, one is not scalar)\ntry:\n print(f\"list[int] | list[list[int]]: {field_annotation_is_scalar_sequence(list[int] | list[list[int]])}\")\nexcept TypeError:\n print(\"Python < 3.10\")\n\n# 3. Optional scalar sequence\n# list[int] | None -> True (list[int] is scalar sequence, None is scalar)\ntry:\n print(f\"list[int] | None: {field_annotation_is_scalar_sequence(list[int] | None)}\")\nexcept TypeError:\n print(\"Python < 3.10\")\n\n# 4. Optional non-scalar sequence\n# list[list[int]] | None -> False (list[list[int]] is not scalar)\ntry:\n print(f\"list[list[int]] | None: {field_annotation_is_scalar_sequence(list[list[int]] | None)}\")\nexcept TypeError:\n print(\"Python < 3.10\")\nPY\n"
} | {"status": "ok", "stdout": "list[int] | int: True\nlist[int] | list[list[int]]: True\nlist[int] | None: True\nlist[list[int]] | None: True\n", "stderr": "", "exfull{"status": "ok", "stdout": "list[int] | int: True\nlist[int] | list[list[int]]: True\nlist[int] | None: True\nlist[list[int]] | None: True\n", "stderr": "", "exit_code": 0} |
| 9 | run_command | {
"command": "python3 - <<'PY'\nimport typing\nimport types\nfrom typing_extensions import get_origin, get_args\n\nUnionType = getattr(types, \"UnionType\", typing.Union)\n\ndef field_annotation_is_scalar(annotation):\n # Simplified version of field_annotation_is_scalar\n # It returns True if it's NOT complex.\n # Complex: BaseModel, Mapping, UploadFile, Sequence, dataclass\n if annotation in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return False\n # For the sake of this test, let's assume None is scalar\n if annotation is type(None):\n return True\n return True\n\ndef field_annotation_is_scalar_sequence(annotation):\n origin = get_origin(annotation)\n if origin is typing.Union or origin is UnionType:\n at_least_one_scalar_sequence = False\n for arg in get_args(annotation):\n if field_annotation_is_scalar_sequence(arg):\n at_least_one_scalar_sequence = True\n continue\n elif not field_annotation_is_scalar(arg):\n return False\n return at_least_one_scalar_sequence\n \n # Simplified check for sequence and all scalar\n if origin in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return all(field_annotation_is_scalar(sub) for sub in get_args(annotation))\n return False\n\n# Test cases\n# 1. Scalar sequence in Union\n# list[int] | int -> True (at least one scalar sequence, others are scalar)\ntry:\n print(f\"list[int] | int: {field_annotation_is_scalar_sequence(list[int] | int)}\") \nexcept TypeError:\n print(\"Python < 3.10\")\n\n# 2. Scalar sequence and non-scalar in Union\n# list[int] | list[list[int]] -> False (one is scalar sequence, one is not scalar)\ntry:\n print(f\"list[int] | list[list[int]]: {field_annotation_is_scalar_sequence(list[int] | list[list[int]])}\")\nexcept TypeError:\n print(\"Python < 3.10\")\n\n# 3. Optional scalar sequence\n# list[int] | None -> True (list[int] is scalar sequence, None is scalar)\ntry:\n print(f\"list[int] | None: {field_annotation_is_scalar_sequence(list[int] | None)}\")\nexcept TypeError:\n print(\"Python < 3.10\")\n\n# 4. Optional non-scalar sequence\n# list[list[int]] | None -> False (list[list[int]] is not scalar)\ntry:\n print(f\"list[list[int]] | None: {field_annotation_is_scalar_sequence(list[list[int]] | None)}\")\nexcept TypeError:\n print(\"Python < 3.10\")\nPY\n"
} | {"status": "ok", "stdout": "list[int] | int: True\nlist[int] | list[list[int]]: True\nlist[int] | None: True\nlist[list[int]] | None: True\n", "stderr": "", "exfull{"status": "ok", "stdout": "list[int] | int: True\nlist[int] | list[list[int]]: True\nlist[int] | None: True\nlist[list[int]] | None: True\n", "stderr": "", "exit_code": 0} |
| 10 | run_command | {
"command": "python3 - <<'PY'\nimport typing\nimport types\nfrom typing_extensions import get_origin, get_args\n\nUnionType = getattr(types, \"UnionType\", typing.Union)\n\ndef field_annotation_is_scalar(annotation):\n if annotation in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return False\n if annotation is type(None):\n return True\n return True\n\ndef field_annotation_is_scalar_sequence(annotation):\n origin = get_origin(annotation)\n if origin is typing.Union or origin is UnionType:\n at_least_one_scalar_sequence = False\n for arg in get_args(annotation):\n if field_annotation_is_scalar_sequence(arg):\n at_least_one_scalar_sequence = True\n continue\n elif not field_annotation_is_scalar(arg):\n # This is where the issue is. \n # If arg is not a scalar sequence AND not a scalar, it should return False.\n # But what if arg is a scalar sequence? \n # The 'if field_annotation_is_scalar_sequence(arg)' already handled it.\n return False\n return at_least_one_scalar_sequence\n \n if origin in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return all(field_annotation_is_scalar(sub) for sub in get_args(annotation))\n return False\n\n# Test cases\n# 1. Scalar sequence in Union\n# list[int] | int -> True\ntry:\n print(f\"list[int] | int: {field_annotation_is_scalar_sequence(list[int] | int)}\") \nexcept TypeError:\n print(\"Python < 3.10\")\n\n# 2. Scalar sequence and non-scalar in Union\n# list[int] | list[list[int]] -> False\ntry:\n print(f\"list[int] | list[list[int]]: {field_annotation_is_scalar_sequence(list[int] | list[list[int]])}\")\nexcept TypeError:\n print(\"Python < 3.10\")\n\n# 3. Optional scalar sequence\n# list[int] | None -> True\ntry:\n print(f\"list[int] | None: {field_annotation_is_scalar_sequence(list[int] | None)}\")\nexcept TypeError:\n print(\"Python < 3.10\")\n\n# 4. Optional non-scalar sequence\n# list[list[int]] | None -> False\ntry:\n print(f\"list[list[int]] | None: {field_annotation_is_scalar_sequence(list[list[int]] | None)}\")\nexcept TypeError:\n print(\"Python < 3.10\")\nPY\n"
} | {"status": "ok", "stdout": "list[int] | int: True\nlist[int] | list[list[int]]: True\nlist[int] | None: True\nlist[list[int]] | None: True\n", "stderr": "", "exfull{"status": "ok", "stdout": "list[int] | int: True\nlist[int] | list[list[int]]: True\nlist[int] | None: True\nlist[list[int]] | None: True\n", "stderr": "", "exit_code": 0} |
| 11 | run_command | {
"command": "python3 - <<'PY'\nimport typing\nimport types\nfrom typing_extensions import get_origin, get_args\n\nUnionType = getattr(types, \"UnionType\", typing.Union)\n\ndef field_annotation_is_scalar(annotation):\n if annotation in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return False\n if annotation is type(None):\n return True\n return True\n\ndef field_annotation_is_scalar_sequence(annotation):\n origin = get_origin(annotation)\n if origin is typing.Union or origin is UnionType:\n at_least_one_scalar_sequence = False\n for arg in get_args(annotation):\n if field_annotation_is_scalar_sequence(arg):\n at_least_one_scalar_sequence = True\n continue\n elif not field_annotation_is_scalar(arg):\n return False\n return at_least_one_scalar_sequence\n \n if origin in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return all(field_annotation_is_scalar(sub) for sub in get_args(annotation))\n return False\n\n# Test cases\n# list[list[int]] | None\n# arg1 = list[list[int]]\n# field_annotation_is_scalar_sequence(list[list[int]]) -> False\n# field_annotation_is_scalar(list[list[int]]) -> False\n# return False\n\n# Wait, why did my previous run return True?\n# Let's re-examine field_annotation_is_scalar(list[list[int]])\n# list[list[int]] is NOT in (list, typing.List, ...)\n# It's a GenericAlias.\n# So field_annotation_is_scalar(list[list[int]]) returns True!\n# That's the bug in my test mock.\n\ndef field_annotation_is_scalar_fixed(annotation):\n origin = get_origin(annotation)\n if origin in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return False\n if annotation in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return False\n if annotation is type(None):\n return True\n return True\n\ndef field_annotation_is_scalar_sequence_fixed(annotation):\n origin = get_origin(annotation)\n if origin is typing.Union or origin is UnionType:\n at_least_one_scalar_sequence = False\n for arg in get_args(annotation):\n if field_annotation_is_scalar_sequence_fixed(arg):\n at_least_one_scalar_sequence = True\n continue\n elif not field_annotation_is_scalar_fixed(arg):\n return False\n return at_least_one_scalar_sequence\n \n if origin in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return all(field_annotation_is_scalar_fixed(sub) for sub in get_args(annotation))\n return False\n\nprint(f\"list[int] | None: {field_annotation_is_scalar_sequence_fixed(list[int] | None)}\") # True\nprint(f\"list[list[int]] | None: {field_annotation_is_scalar_sequence_fixed(list[list[int]] | None)}\") # False\nPY\n"
} | {"status": "ok", "stdout": "list[int] | None: True\nlist[list[int]] | None: False\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "list[int] | None: True\nlist[list[int]] | None: False\n", "stderr": "", "exit_code": 0} |
| 12 | run_command | {
"command": "grep -rn \"field_annotation_is_scalar_sequence\" fastapi/_compat/shared.py"
} | {"status": "ok", "stdout": "fastapi/_compat/shared.py:130:def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool:\nfastapi/_compat/full{"status": "ok", "stdout": "fastapi/_compat/shared.py:130:def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool:\nfastapi/_compat/shared.py:135: if field_annotation_is_scalar_sequence(arg):\n", "stderr": "", "exit_code": 0} |
| 13 | run_command | {
"command": "sed -n '130,145p' fastapi/_compat/shared.py"
} | {"status": "ok", "stdout": "def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool:\n origin = get_origin(annotation)\n if origin ifull{"status": "ok", "stdout": "def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool:\n origin = get_origin(annotation)\n if origin is Union or origin is UnionType:\n at_least_one_scalar_sequence = False\n for arg in get_args(annotation):\n if field_annotation_is_scalar_sequence(arg):\n at_least_one_scalar_sequence = True\n continue\n elif not field_annotation_is_scalar(arg):\n return False\n return at_least_one_scalar_sequence\n return field_annotation_is_sequence(annotation) and all(\n field_annotation_is_scalar(sub_annotation)\n for sub_annotation in get_args(annotation)\n )\n\n", "stderr": "", "exit_code": 0} |
| 14 | run_command | {
"command": "python3 - <<'PY'\nimport typing\nimport types\nfrom typing_extensions import get_origin, get_args\n\nUnionType = getattr(types, \"UnionType\", typing.Union)\n\ndef field_annotation_is_scalar(annotation):\n # Mocking field_annotation_is_complex\n def field_annotation_is_complex(ann):\n origin = get_origin(ann)\n if origin in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return True\n if ann in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return True\n return False\n return not field_annotation_is_complex(annotation)\n\ndef field_annotation_is_sequence(annotation):\n origin = get_origin(annotation)\n if origin in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return True\n if annotation in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return True\n return False\n\ndef field_annotation_is_scalar_sequence(annotation):\n origin = get_origin(annotation)\n if origin is typing.Union or origin is UnionType:\n at_least_one_scalar_sequence = False\n for arg in get_args(annotation):\n if field_annotation_is_scalar_sequence(arg):\n at_least_one_scalar_sequence = True\n continue\n elif not field_annotation_is_scalar(arg):\n return False\n return at_least_one_scalar_sequence\n return field_annotation_is_sequence(annotation) and all(\n field_annotation_is_scalar(sub_annotation)\n for sub_annotation in get_args(annotation)\n )\n\n# Test case: list[int] | None\n# arg1 = list[int]\n# field_annotation_is_scalar_sequence(list[int]) -> True\n# at_least_one_scalar_sequence = True\n# arg2 = NoneType\n# field_annotation_is_scalar_sequence(NoneType) -> False\n# field_annotation_is_scalar(NoneType) -> True\n# return True (Correct)\n\n# Test case: list[list[int]] | None\n# arg1 = list[list[int]]\n# field_annotation_is_scalar_sequence(list[list[int]]) -> False (because sub_annotation list[int] is not scalar)\n# field_annotation_is_scalar(list[list[int]]) -> False (because it's complex)\n# return False (Correct)\n\n# Wait, if the current implementation is correct, what is the issue?\n# \"Fix optional sequence handling with new union syntax from Python 3.10\"\n# \"is only taking one union type into account\"\n\n# Let's try a more complex union: list[int] | int | None\n# arg1 = list[int] -> scalar sequence -> True, at_least_one = True\n# arg2 = int -> not scalar sequence, but scalar -> True\n# arg3 = None -> not scalar sequence, but scalar -> True\n# return True (Correct)\n\n# What if it's list[int] | list[str]?\n# arg1 = list[int] -> True, at_least_one = True\n# arg2 = list[str] -> True, at_least_one = True\n# return True (Correct)\n\n# What if it's list[int] | list[list[int]]?\n# arg1 = list[int] -> True, at_least_one = True\n# arg2 = list[list[int]] -> not scalar sequence, NOT scalar -> return False (Correct)\n\n# I don't see the bug in the provided code. Let me re-read the issue.\n# \"is only taking one union type into account\"\n# Maybe it's about how UnionType is handled?\n# In fastapi/types.py: UnionType = getattr(types, \"UnionType\", Union)\n# In fastapi/_compat/shared.py: from fastapi.types import UnionType\n# Then: if origin is Union or origin is UnionType:\n# If Python < 3.10, UnionType is Union. So 'origin is Union or origin is Union' is just 'origin is Union'.\n# If Python >= 3.10, UnionType is types.UnionType. So 'origin is Union or origin is UnionType' covers both.\n\n# Wait, let me check the PR mentioned: https://github.com/fastapi/fastapi/pull/14297\n# I can't access the internet, but the issue description says it's \"only taking one union type into account\".\n# Maybe it's in another function?\n\n# Let's check field_annotation_is_sequence again.\ndef field_annotation_is_sequence_impl(annotation):\n origin = get_origin(annotation)\n if origin is typing.Union or orig | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 15 | run_command | {
"command": "grep -rn \"UnionType\" fastapi | grep \"is\" | grep \"Union\" | head -20"
} | {"status": "ok", "stdout": "fastapi/dependencies/utils.py:839: if origin is not Union and origin is not UnionType:\nfastapi/_compat/shared.py:85: if origin is Ufull{"status": "ok", "stdout": "fastapi/dependencies/utils.py:839: if origin is not Union and origin is not UnionType:\nfastapi/_compat/shared.py:85: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:111: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:132: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:151: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:162: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:171: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:186: if origin is Union or origin is UnionType:\nfastapi/_compat/shared.py:203: if origin is Union or origin is UnionType:\n", "stderr": "", "exit_code": 0} |
| 16 | run_command | {
"command": "sed -n '150,210p' fastapi/_compat/shared.py"
} | {"status": "ok", "stdout": " origin = get_origin(annotation)\n if origin is Union or origin is UnionType:\n for arg in get_args(annotation):\n if lenient_issubcfull{"status": "ok", "stdout": " origin = get_origin(annotation)\n if origin is Union or origin is UnionType:\n for arg in get_args(annotation):\n if lenient_issubclass(arg, bytes):\n return True\n return False\n\n\ndef is_uploadfile_or_nonable_uploadfile_annotation(annotation: Any) -> bool:\n if lenient_issubclass(annotation, UploadFile):\n return True\n origin = get_origin(annotation)\n if origin is Union or origin is UnionType:\n for arg in get_args(annotation):\n if lenient_issubclass(arg, UploadFile):\n return True\n return False\n\n\ndef is_bytes_sequence_annotation(annotation: Any) -> bool:\n origin = get_origin(annotation)\n if origin is Union or origin is UnionType:\n at_least_one = False\n for arg in get_args(annotation):\n if is_bytes_sequence_annotation(arg):\n at_least_one = True\n continue\n return at_least_one\n return field_annotation_is_sequence(annotation) and all(\n is_bytes_or_nonable_bytes_annotation(sub_annotation)\n for sub_annotation in get_args(annotation)\n )\n\n\ndef is_uploadfile_sequence_annotation(annotation: Any) -> bool:\n origin = get_origin(annotation)\n if origin is Union or origin is UnionType:\n at_least_one = False\n for arg in get_args(annotation):\n if is_uploadfile_sequence_annotation(arg):\n at_least_one = True\n continue\n return at_least_one\n return field_annotation_is_sequence(annotation) and all(\n is_uploadfile_or_nonable_uploadfile_annotation(sub_annotation)\n for sub_annotation in get_args(annotation)\n )\n\n\ndef annotation_is_pydantic_v1(annotation: Any) -> bool:\n if lenient_issubclass(annotation, may_v1.BaseModel):\n return True\n origin = get_origin(annotation)\n if origin is Union or origin is UnionType:\n for arg in get_args(a |
| 17 | run_command | {
"command": "python3 - <<'PY'\nimport typing\nimport types\nfrom typing_extensions import get_origin, get_args\n\nUnionType = getattr(types, \"UnionType\", typing.Union)\n\ndef is_bytes_or_nonable_bytes_annotation(annotation):\n # Simplified\n return annotation is bytes or annotation is type(None)\n\ndef is_bytes_sequence_annotation(annotation):\n origin = get_origin(annotation)\n if origin is typing.Union or origin is UnionType:\n at_least_one = False\n for arg in get_args(annotation):\n if is_bytes_sequence_annotation(arg):\n at_least_one = True\n continue\n return at_least_one\n # Simplified field_annotation_is_sequence\n if origin in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return all(is_bytes_or_nonable_bytes_annotation(sub) for sub in get_args(annotation))\n return False\n\n# Test: list[bytes] | int\n# arg1 = list[bytes] -> is_bytes_sequence_annotation(list[bytes]) -> True\n# at_least_one = True\n# arg2 = int -> is_bytes_sequence_annotation(int) -> False\n# return True (Correct)\n\n# Test: list[bytes] | list[int]\n# arg1 = list[bytes] -> True, at_least_one = True\n# arg2 = list[int] -> is_bytes_sequence_annotation(list[int]) -> False\n# return True (Correct)\n\n# Wait, if it's list[bytes] | list[int], should it be True?\n# is_bytes_sequence_annotation means \"is this a sequence of bytes?\"\n# If it's a Union of a bytes-sequence and a non-bytes-sequence, is it a bytes-sequence?\n# Probably not. It should probably be False if any member is not a bytes-sequence or scalar.\n\n# Let's look at is_bytes_sequence_annotation again:\n# if origin is Union or origin is UnionType:\n# at_least_one = False\n# for arg in get_args(annotation):\n# if is_bytes_sequence_annotation(arg):\n# at_least_one = True\n# continue\n# return at_least_one\n\n# This implementation returns True if ANY member of the Union is a bytes-sequence.\n# It doesn't check if other members are scalars.\n# This is different from field_annotation_is_scalar_sequence.\n\n# Let's check is_uploadfile_sequence_annotation. It's the same.\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 18 | run_command | {
"command": "python3 - <<'PY'\nimport typing\nimport types\nfrom typing_extensions import get_origin, get_args\n\nUnionType = getattr(types, \"UnionType\", typing.Union)\n\ndef is_bytes_or_nonable_bytes_annotation(annotation):\n return annotation is bytes or annotation is type(None)\n\ndef is_bytes_sequence_annotation(annotation):\n origin = get_origin(annotation)\n if origin is typing.Union or origin is UnionType:\n at_least_one = False\n for arg in get_args(annotation):\n if is_bytes_sequence_annotation(arg):\n at_least_one = True\n continue\n return at_least_one\n \n # Mock field_annotation_is_sequence\n if origin in (list, typing.List, tuple, typing.Tuple, set, typing.Set):\n return all(is_bytes_or_nonable_bytes_annotation(sub) for sub in get_args(annotation))\n return False\n\n# Test: list[bytes] | list[int]\n# arg1 = list[bytes] -> True, at_least_one = True\n# arg2 = list[int] -> False\n# return True\n\n# If this is supposed to be like field_annotation_is_scalar_sequence, it should be False.\n# But is it?\n\n# Let's re-read the issue: \"Fix optional sequence handling with new union syntax from Python 3.10\"\n# \"is only taking one union type into account\"\n\n# Maybe it's about the `typing-inspection` recommendation.\n# Pydantic's typing-inspection handles Unions better.\n\n# Let's look at the code again.\n# In field_annotation_is_scalar_sequence:\n# if origin is Union or origin is UnionType:\n# at_least_one_scalar_sequence = False\n# for arg in get_args(annotation):\n# if field_annotation_is_scalar_sequence(arg):\n# at_least_one_scalar_sequence = True\n# continue\n# elif not field_annotation_is_scalar(arg):\n# return False\n# return at_least_one_scalar_sequence\n\n# This looks correct for \"Optional sequence\".\n# If it's `list[int] | None`, then:\n# arg1 = list[int] -> field_annotation_is_scalar_sequence(list[int]) -> True, at_least_one = True\n# arg2 = None -> field_annotation_is_scalar_sequence(None) -> False, field_annotation_is_scalar(None) -> True\n# return True.\n\n# What if it's `list[int] | int`?\n# arg1 = list[int] -> True, at_least_one = True\n# arg2 = int -> False, field_annotation_is_scalar(int) -> True\n# return True.\n\n# What if it's `list[int] | list[list[int]]`?\n# arg1 = list[int] -> True, at_least_one = True\n# arg2 = list[list[int]] -> False, field_annotation_is_scalar(list[list[int]]) -> False\n# return False.\n\n# I'm struggling to find the bug. Let me search for other places where Union is handled.\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 19 | run_command | {
"command": "grep -rn \"get_origin\" fastapi | grep \"Union\" | head -20"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 20 | run_command | {
"command": "grep -rn \"get_origin\" fastapi | grep \"UnionType\" | head -20"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 21 | run_command | {
"command": "grep -rn \"get_origin\" fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/dependencies/utils.py:78:from typing_extensions import Annotated, Literal, get_args, get_origin\nfastapi/dependencies/utils.full{"status": "ok", "stdout": "fastapi/dependencies/utils.py:78:from typing_extensions import Annotated, Literal, get_args, get_origin\nfastapi/dependencies/utils.py:357: if get_origin(use_annotation) is Annotated:\nfastapi/dependencies/utils.py:836: origin = get_origin(field_type)\nfastapi/_compat/shared.py:24:from typing_extensions import Annotated, get_args, get_origin\nfastapi/_compat/shared.py:84: origin = get_origin(annotation)\nfastapi/_compat/shared.py:91: get_origin(annotation)\nfastapi/_compat/shared.py:110: origin = get_origin(annotation)\nfastapi/_compat/shared.py:131: origin = get_origin(annotation)\nfastapi/_compat/shared.py:150: origin = get_origin(annotation)\nfastapi/_compat/shared.py:161: origin = get_origin(annotation)\nfastapi/_compat/shared.py:170: origin = get_origin(annotation)\nfastapi/_compat/shared.py:185: origin = get_origin(annotation)\nfastapi/_compat/shared.py:202: origin = get_origin(annotation)\nfastapi/_compat/v2.py:36:from typing_extensions import Annotated, Literal, get_args, get_origin\nfastapi/_compat/v2.py:383: origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation\nfastapi/_compat/v2.py:389: origin_type = get_origin(union_arg) or union_arg\nfastapi/_compat/v2.py:459: origin = get_origin(annotation)\n", "stderr": "", "exit_code": 0} |