failed NO_PATCH NO_PATCH UNSUBMITTED empty_patch(budget) · 24 tool calls · 313 s · fastapi/fastapi
🐛 Fix tagged discriminated union not recognized as body field
## Problem
When using a union type models with a tagged discriminator as an endpoint parameter, like the following:
```py
class Pie(BaseModel):
time_to_cook: int
num_ingredients: int
class ApplePie(Pie):
fruit: Literal["apple"] = "apple"
class PumpkinPie(Pie):
filling: Literal["pumpkin"] = "pumpkin"
def get_discriminator_value(v: Any) -> str:
if isinstance(v, dict):
return v.get("fruit", v.get("filling"))
return getattr(v, "fruit", getattr(v, "filling", None))
Dessert = Annotated[
Annotated[ApplePie, Tag("apple")] | Annotated[PumpkinPie, Tag("pumpkin")],
Discriminator(get_discriminator_value),
]
app = FastAPI()
@app.post("/dessert")
async def dinner(dessert: Dessert) -> dict[str, Any]:
return dessert.model_dump()
```
FastAPI fails to identify it correctly as **body** payload. It incorrectly considers it's a query parameter, which then causes a validation error. The workaround currently is to explicitly tell FastAPI it's a body parameter using `dessert: Annotated[Dessert, Body()]`
## Fix
The solution is to improve the function handling type annotations by handling the case where we can have `Annotated`
types inside an `Annotated` type.
---
Discussed in https://github.com/fastapi/fastapi/discussions/12941| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "cat <<EOF > /tmp/repro.py\nfrom typing import Any, Literal, Annotated\nfrom pydantic import BaseModel\nfrom fastapi import FastAPI, Body\nfrom fastapi.params import Tag, Discriminator\nfrom fastapi.testclient import TestClient\n\nclass Pie(BaseModel):\n time_to_cook: int\n num_ingredients: int\n\nclass ApplePie(Pie):\n fruit: Literal[\"apple\"] = \"apple\"\n\nclass PumpkinPie(Pie):\n filling: Literal[\"pumpkin\"] = \"pumpkin\"\n\ndef get_discriminator_value(v: Any) -> str:\n if isinstance(v, dict):\n return v.get(\"fruit\", v.get(\"filling\"))\n return getattr(v, \"fruit\", getattr(v, \"filling\", None))\n\nDessert = Annotated[\n Annotated[ApplePie, Tag(\"apple\")] | Annotated[PumpkinPie, Tag(\"pumpkin\")],\n Discriminator(get_discriminator_value),\n]\n\napp = FastAPI()\n@app.post(\"/dessert\")\nasync def dinner(dessert: Dessert) -> dict[str, Any]:\n return dessert.model_dump()\n\nclient = TestClient(app)\n\ndef test_repro():\n # This should be recognized as a body parameter\n response = client.post(\"/dessert\", json={\"time_to_cook\": 10, \"num_ingredients\": 5, \"fruit\": \"apple\"})\n assert response.status_code == 200\n assert response.json() == {\"time_to_cook\": 10, \"num_ingredients\": 5, \"fruit\": \"apple\"}\n\nif __name__ == \"__main__\":\n try:\n test_repro()\n print(\"Success\")\n except Exception as e:\n print(f\"Failure: {e}\")\nEOF\npython3 /tmp/repro.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recenfull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"/tmp/swe_work/eval_submission_nothink/fastapi_12942/a/tmp/repro.py\", line 4, in <module>\n from fastapi.params import Tag, Discriminator\nImportError: cannot import name 'Tag' from 'fastapi.params' (/workspace/fastapi/params.py)\n", "exit_code": 1}} |
| 2 | run_command | {
"command": "grep -rn \"Tag\" fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/openapi/models.py:429:class Tag(BaseModelWithConfig):\nfastapi/openapi/models.py:445: tags: Optional[List[Tag]] = None\n", "full{"status": "ok", "stdout": "fastapi/openapi/models.py:429:class Tag(BaseModelWithConfig):\nfastapi/openapi/models.py:445: tags: Optional[List[Tag]] = None\n", "exit_code": 0} |
| 3 | run_command | {
"command": "grep -rn \"Discriminator\" fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/openapi/models.py:106:class Discriminator(BaseModel):\nfastapi/openapi/models.py:205: discriminator: Optional[Discriminator]full{"status": "ok", "stdout": "fastapi/openapi/models.py:106:class Discriminator(BaseModel):\nfastapi/openapi/models.py:205: discriminator: Optional[Discriminator] = None\n", "exit_code": 0} |
| 4 | run_command | {
"command": "grep -rn \"Annotated\" fastapi | grep \"get_type_hints\" | head -20"
} | {"status": "ok", "stdout": "", "exit_code": 0}full{"status": "ok", "stdout": "", "exit_code": 0} |
| 5 | run_command | {
"command": "grep -rn \"Annotated\" fastapi/dependencies/utils.py | head -20"
} | {"status": "ok", "stdout": "75:from typing_extensions import Annotated, get_args, get_origin\n370: # Extract Annotated info\n371: if get_origin(use_annotation) full{"status": "ok", "stdout": "75:from typing_extensions import Annotated, get_args, get_origin\n370: # Extract Annotated info\n371: if get_origin(use_annotation) is Annotated:\n390: # Set default for Annotated FieldInfo\n400: f\" `Annotated` for {param_name!r}. Set the default value with `=` instead.\"\n407: # Get Annotated Depends\n413: \"Cannot specify `Depends` in `Annotated` and default value\"\n417: \"Cannot specify a FastAPI annotation in `Annotated` and `Depends` as a\"\n424: \"Cannot specify FastAPI annotations in `Annotated` and default value\"\n453: # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value\n", "exit_code": 0} |
| 6 | read_file | {
"filepath": "fastapi/dependencies/utils.py",
"start_line": 360,
"end_line": 460
} | {"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " value: Any,\n is_path_param: bool,\n) -> ParamDetails:\n field_info = None\n depends full{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " value: Any,\n is_path_param: bool,\n) -> ParamDetails:\n field_info = None\n depends = None\n type_annotation: Any = Any\n use_annotation: Any = Any\n if annotation is not inspect.Signature.empty:\n use_annotation = annotation\n type_annotation = annotation\n # Extract Annotated info\n if get_origin(use_annotation) is Annotated:\n annotated_args = get_args(annotation)\n type_annotation = annotated_args[0]\n fastapi_annotations = [\n arg\n for arg in annotated_args[1:]\n if isinstance(arg, (FieldInfo, params.Depends))\n ]\n fastapi_specific_annotations = [\n arg\n for arg in fastapi_annotations\n if isinstance(arg, (params.Param, params.Body, params.Depends))\n ]\n if fastapi_specific_annotations:\n fastapi_annotation: Union[FieldInfo, params.Depends, None] = (\n fastapi_specific_annotations[-1]\n )\n else:\n fastapi_annotation = None\n # Set default for Annotated FieldInfo\n if isinstance(fastapi_annotation, FieldInfo):\n # Copy `field_info` because we mutate `field_info.default` below.\n field_info = copy_field_info(\n field_info=fastapi_annotation, annotation=use_annotation\n )\n assert (\n field_info.default is Undefined or field_info.default is RequiredParam\n ), (\n f\"`{field_info.__class__.__name__}` default value cannot be set in\"\n f\" `Annotated` for {param_name!r}. Set the default value with `=` instead.\"\n )\n if value is not inspect.Signature.empty:\n assert not is_path_param, \"Path parameters cannot have default values\"\n field_info.default = value\n else:\n field_ |
| 7 | run_command | {
"command": "grep -rn \"Discriminator\" fastapi/params.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "", "exit_code": 1}}full{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "", "exit_code": 1}} |
| 8 | run_command | {
"command": "grep -rn \"Discriminator\" fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/openapi/models.py:106:class Discriminator(BaseModel):\nfastapi/openapi/models.py:205: discriminator: Optional[Discriminator]full{"status": "ok", "stdout": "fastapi/openapi/models.py:106:class Discriminator(BaseModel):\nfastapi/openapi/models.py:205: discriminator: Optional[Discriminator] = None\n", "exit_code": 0} |
| 9 | run_command | {
"command": "grep -rn \"Discriminator\" . | grep \".py\" | head -20"
} | {"status": "ok", "stdout": "./fastapi/openapi/models.py:106:class Discriminator(BaseModel):\n./fastapi/openapi/models.py:205: discriminator: Optional[Discriminafull{"status": "ok", "stdout": "./fastapi/openapi/models.py:106:class Discriminator(BaseModel):\n./fastapi/openapi/models.py:205: discriminator: Optional[Discriminator] = None\n", "exit_code": 0} |
| 10 | run_command | {
"command": "grep -rn \"Pydantic\" fastapi/dependencies/utils.py | head -20"
} | {"status": "ok", "stdout": "514: # For Pydantic v1\n765: # If headers are in a Pydantic model, the way to disable convert_underscores\n766: # would be with Headfull{"status": "ok", "stdout": "514: # For Pydantic v1\n765: # If headers are in a Pydantic model, the way to disable convert_underscores\n766: # would be with Header(convert_underscores=False) at the Pydantic model level\n778: # Handle fields extracted from a Pydantic Model for a header, each field\n", "exit_code": 0} |
| 11 | run_command | {
"command": "grep -rn \"Body\" fastapi/params.py | head -20"
} | {"status": "ok", "stdout": "479:class Body(FieldInfo): # type: ignore[misc]\n596:class Form(Body): # type: ignore[misc]\n", "exit_code": 0}full{"status": "ok", "stdout": "479:class Body(FieldInfo): # type: ignore[misc]\n596:class Form(Body): # type: ignore[misc]\n", "exit_code": 0} |
| 12 | read_file | {
"end_line": 500,
"filepath": "fastapi/params.py",
"start_line": 450
} | {"status": "ok", "filepath": "fastapi/params.py", "content": " alias_priority=alias_priority,\n validation_alias=validation_alias,\n serialization_alias=serialifull{"status": "ok", "filepath": "fastapi/params.py", "content": " alias_priority=alias_priority,\n validation_alias=validation_alias,\n serialization_alias=serialization_alias,\n title=title,\n description=description,\n gt=gt,\n ge=ge,\n lt=lt,\n le=le,\n min_length=min_length,\n max_length=max_length,\n pattern=pattern,\n regex=regex,\n discriminator=discriminator,\n strict=strict,\n multiple_of=multiple_of,\n allow_inf_nan=allow_inf_nan,\n max_digits=max_digits,\n decimal_places=decimal_places,\n deprecated=deprecated,\n example=example,\n examples=examples,\n openapi_examples=openapi_examples,\n include_in_schema=include_in_schema,\n json_schema_extra=json_schema_extra,\n **extra,\n )\n\n\nclass Body(FieldInfo): # type: ignore[misc]\n def __init__(\n self,\n default: Any = Undefined,\n *,\n default_factory: Union[Callable[[], Any], None] = _Unset,\n annotation: Optional[Any] = None,\n embed: Union[bool, None] = None,\n media_type: str = \"application/json\",\n alias: Optional[str] = None,\n alias_priority: Union[int, None] = _Unset,\n # TODO: update when deprecating Pydantic v1, import these types\n # validation_alias: str | AliasPath | AliasChoices | None\n validation_alias: Union[str, None] = None,\n serialization_alias: Union[str, None] = None,\n title: Optional[str] = None,\n description: Optional[str] = None,\n gt: Optional[float] = None,\n ge: Optional[float] = None,\n lt: Optional[float] = None,\n le: Optional[float] = None,\n min_length: Optional[int] = None,\n", "start_line": 450, "end_line": 500, "total_lines": 786, "is_trunca |
| 13 | run_command | {
"command": "grep -rn \"Discriminator\" . | grep \"pydantic\" | head -20"
} | {"status": "ok", "stdout": "", "exit_code": 0}full{"status": "ok", "stdout": "", "exit_code": 0} |
| 14 | run_command | {
"command": "cat <<EOF > /tmp/repro.py\nfrom typing import Any, Literal, Annotated, Union\nfrom pydantic import BaseModel\nfrom fastapi import FastAPI, Body\nfrom fastapi.testclient import TestClient\n\nclass Pie(BaseModel):\n time_to_cook: int\n num_ingredients: int\n\nclass ApplePie(Pie):\n fruit: Literal[\"apple\"] = \"apple\"\n\nclass PumpkinPie(Pie):\n filling: Literal[\"pumpkin\"] = \"pumpkin\"\n\ndef get_discriminator_value(v: Any) -> str:\n if isinstance(v, dict):\n return v.get(\"fruit\", v.get(\"filling\"))\n return getattr(v, \"fruit\", getattr(v, \"filling\", None))\n\n# Using Pydantic's Discriminator if available, or just a type that looks like it\n# The issue says:\n# Dessert = Annotated[\n# Annotated[ApplePie, Tag(\"apple\")] | Annotated[PumpkinPie, Tag(\"pumpkin\")],\n# Discriminator(get_discriminator_value),\n# ]\n# But Tag and Discriminator are not in fastapi.params.\n# They might be from pydantic.\n\ntry:\n from pydantic import Discriminator\nexcept ImportError:\n # Mock Discriminator for the sake of reproduction if not present\n class Discriminator:\n def __init__(self, func):\n self.func = func\n\n# Mock Tag since it's not found\nclass Tag:\n def __init__(self, value):\n self.value = value\n\nDessert = Annotated[\n Annotated[ApplePie, Tag(\"apple\")] | Annotated[PumpkinPie, Tag(\"pumpkin\")],\n Discriminator(get_discriminator_value),\n]\n\napp = FastAPI()\n@app.post(\"/dessert\")\nasync def dinner(dessert: Dessert) -> dict[str, Any]:\n return dessert.model_dump()\n\nclient = TestClient(app)\n\ndef test_repro():\n # This should be recognized as a body parameter\n response = client.post(\"/dessert\", json={\"time_to_cook\": 10, \"num_ingredients\": 5, \"fruit\": \"apple\"})\n print(f\"Status code: {response.status_code}\")\n print(f\"Response: {response.json()}\")\n assert response.status_code == 200\n assert response.json() == {\"time_to_cook\": 10, \"num_ingredients\": 5, \"fruit\": \"apple\"}\n\nif __name__ == \"__main__\":\n try:\n test_repro()\n print(\"Success\")\n except Exception as e:\n import traceback\n traceback.print_exc()\n print(f\"Failure: {e}\")\nEOF\npython3 /tmp/repro.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recenfull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"/tmp/swe_work/eval_submission_nothink/fastapi_12942/a/tmp/repro.py\", line 49, in <module>\n @app.post(\"/dessert\")\n ~~~~~~~~^^^^^^^^^^^^\n File \"/workspace/fastapi/routing.py\", line 1070, in decorator\n self.add_api_route(\n ~~~~~~~~~~~~~~~~~~^\n path,\n ^^^^^\n ...<23 lines>...\n generate_unique_id_function=generate_unique_id_function,\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n )\n ^\n File \"/workspace/fastapi/routing.py\", line 1009, in add_api_route\n route = route_class(\n self.prefix + path,\n ...<24 lines>...\n generate_unique_id_function=current_generate_unique_id,\n )\n File \"/workspace/fastapi/routing.py\", line 630, in __init__\n self.dependant = get_dependant(path=self.path_format, call=self.endpoint)\n ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/workspace/fastapi/dependencies/utils.py\", line 293, in get_dependant\n param_details = analyze_param(\n param_name=param_name,\n ...<2 lines>...\n is_path_param=is_path_param,\n )\n File \"/workspace/fastapi/dependencies/utils.py\", line 496, in analyze_param\n field = create_model_field(\n name=param_name,\n ...<4 lines>...\n field_info=field_info,\n )\n File \"/workspace/fastapi/utils.py\", line 98, in create_model_field\n raise fastapi.exceptions.FastAPIError(\n ...<7 lines>...\n ) from None\nfastapi.exceptions.FastAPIError: Invalid args for response field! Hint: check that typing.Annotated[typing.Union[typing.Annotated[__main__.ApplePie, <__main__.Tag object at 0x7af19417bb60>], typing.Annotated[__main__.PumpkinPie, <__main__.Tag object at 0x7af194973ed0>]], Discriminator(discriminator=<function get_discriminator_value at 0x7a |
| 15 | run_command | {
"command": "grep -rn \"Discriminator\" /workspace/fastapi/params.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "grep: /tmp/swe_work/efull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "grep: /tmp/swe_work/eval_submission_nothink/fastapi_12942/a/workspace/fastapi/params.py: No such file or directory\n", "exit_code": 2}} |
| 16 | run_command | {
"command": "grep -rn \"Discriminator\" /workspace/fastapi | head -20"
} | {"status": "ok", "stdout": "", "exit_code": 0}full{"status": "ok", "stdout": "", "exit_code": 0} |
| 17 | run_command | {
"command": "grep -rn \"Annotated\" fastapi/dependencies/utils.py | grep -C 5 \"get_origin\" | head -40"
} | {"status": "ok", "stdout": "75:from typing_extensions import Annotated, get_args, get_origin\n370: # Extract Annotated info\n371: if get_origin(use_annotation) full{"status": "ok", "stdout": "75:from typing_extensions import Annotated, get_args, get_origin\n370: # Extract Annotated info\n371: if get_origin(use_annotation) is Annotated:\n390: # Set default for Annotated FieldInfo\n400: f\" `Annotated` for {param_name!r}. Set the default value with `=` instead.\"\n407: # Get Annotated Depends\n413: \"Cannot specify `Depends` in `Annotated` and default value\"\n417: \"Cannot specify a FastAPI annotation in `Annotated` and `Depends` as a\"\n", "exit_code": 0} |
| 18 | read_file | {
"end_line": 410,
"filepath": "fastapi/dependencies/utils.py",
"start_line": 360
} | {"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " value: Any,\n is_path_param: bool,\n) -> ParamDetails:\n field_info = None\n depends full{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " value: Any,\n is_path_param: bool,\n) -> ParamDetails:\n field_info = None\n depends = None\n type_annotation: Any = Any\n use_annotation: Any = Any\n if annotation is not inspect.Signature.empty:\n use_annotation = annotation\n type_annotation = annotation\n # Extract Annotated info\n if get_origin(use_annotation) is Annotated:\n annotated_args = get_args(annotation)\n type_annotation = annotated_args[0]\n fastapi_annotations = [\n arg\n for arg in annotated_args[1:]\n if isinstance(arg, (FieldInfo, params.Depends))\n ]\n fastapi_specific_annotations = [\n arg\n for arg in fastapi_annotations\n if isinstance(arg, (params.Param, params.Body, params.Depends))\n ]\n if fastapi_specific_annotations:\n fastapi_annotation: Union[FieldInfo, params.Depends, None] = (\n fastapi_specific_annotations[-1]\n )\n else:\n fastapi_annotation = None\n # Set default for Annotated FieldInfo\n if isinstance(fastapi_annotation, FieldInfo):\n # Copy `field_info` because we mutate `field_info.default` below.\n field_info = copy_field_info(\n field_info=fastapi_annotation, annotation=use_annotation\n )\n assert (\n field_info.default is Undefined or field_info.default is RequiredParam\n ), (\n f\"`{field_info.__class__.__name__}` default value cannot be set in\"\n f\" `Annotated` for {param_name!r}. Set the default value with `=` instead.\"\n )\n if value is not inspect.Signature.empty:\n assert not is_path_param, \"Path parameters cannot have default values\"\n field_info.default = value\n else:\n field_ |
| 19 | run_command | {
"command": "cat <<EOF > /tmp/repro.py\nfrom typing import Any, Literal, Annotated, Union\nfrom pydantic import BaseModel, Discriminator\nfrom fastapi import FastAPI, Body\nfrom fastapi.testclient import TestClient\n\nclass Pie(BaseModel):\n time_to_cook: int\n num_ingredients: int\n\nclass ApplePie(Pie):\n fruit: Literal[\"apple\"] = \"apple\"\n\nclass PumpkinPie(Pie):\n filling: Literal[\"pumpkin\"] = \"pumpkin\"\n\ndef get_discriminator_value(v: Any) -> str:\n if isinstance(v, dict):\n return v.get(\"fruit\", v.get(\"filling\"))\n return getattr(v, \"fruit\", getattr(v, \"filling\", None))\n\n# Mock Tag since it's not a standard Pydantic/FastAPI thing in the way used here\n# but the issue says Annotated[ApplePie, Tag(\"apple\")]\nclass Tag:\n def __init__(self, value):\n self.value = value\n\nDessert = Annotated[\n Union[Annotated[ApplePie, Tag(\"apple\")], Annotated[PumpkinPie, Tag(\"pumpkin\")]],\n Discriminator(get_discriminator_value),\n]\n\napp = FastAPI()\n@app.post(\"/dessert\")\nasync def dinner(dessert: Dessert) -> dict[str, Any]:\n return dessert.model_dump()\n\nclient = TestClient(app)\n\ndef test_repro():\n # This should be recognized as a body parameter\n response = client.post(\"/dessert\", json={\"time_to_cook\": 10, \"num_ingredients\": 5, \"fruit\": \"apple\"})\n print(f\"Status code: {response.status_code}\")\n print(f\"Response: {response.json()}\")\n assert response.status_code == 200\n assert response.json() == {\"time_to_cook\": 10, \"num_ingredients\": 5, \"fruit\": \"apple\"}\n\nif __name__ == \"__main__\":\n try:\n test_repro()\n print(\"Success\")\n except Exception as e:\n import traceback\n traceback.print_exc()\n print(f\"Failure: {e}\")\nEOF\npython3 /tmp/repro.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recenfull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"/tmp/swe_work/eval_submission_nothink/fastapi_12942/a/tmp/repro.py\", line 33, in <module>\n @app.post(\"/dessert\")\n ~~~~~~~~^^^^^^^^^^^^\n File \"/workspace/fastapi/routing.py\", line 1070, in decorator\n self.add_api_route(\n ~~~~~~~~~~~~~~~~~~^\n path,\n ^^^^^\n ...<23 lines>...\n generate_unique_id_function=generate_unique_id_function,\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n )\n ^\n File \"/workspace/fastapi/routing.py\", line 1009, in add_api_route\n route = route_class(\n self.prefix + path,\n ...<24 lines>...\n generate_unique_id_function=current_generate_unique_id,\n )\n File \"/workspace/fastapi/routing.py\", line 630, in __init__\n self.dependant = get_dependant(path=self.path_format, call=self.endpoint)\n ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/workspace/fastapi/dependencies/utils.py\", line 293, in get_dependant\n param_details = analyze_param(\n param_name=param_name,\n ...<2 lines>...\n is_path_param=is_path_param,\n )\n File \"/workspace/fastapi/dependencies/utils.py\", line 496, in analyze_param\n field = create_model_field(\n name=param_name,\n ...<4 lines>...\n field_info=field_info,\n )\n File \"/workspace/fastapi/utils.py\", line 98, in create_model_field\n raise fastapi.exceptions.FastAPIError(\n ...<7 lines>...\n ) from None\nfastapi.exceptions.FastAPIError: Invalid args for response field! Hint: check that typing.Annotated[typing.Union[typing.Annotated[__main__.ApplePie, <__main__.Tag object at 0x7de203773b60>], typing.Annotated[__main__.PumpkinPie, <__main__.Tag object at 0x7de203f6bed0>]], Discriminator(discriminator=<function get_discriminator_value at 0x7d |
| 20 | run_command | {
"command": "cat <<EOF > /tmp/repro.py\nfrom typing import Any, Literal, Annotated, Union\nfrom pydantic import BaseModel, Discriminator\nfrom fastapi import FastAPI, Body\nfrom fastapi.testclient import TestClient\n\nclass Pie(BaseModel):\n time_to_cook: int\n num_ingredients: int\n\nclass ApplePie(Pie):\n fruit: Literal[\"apple\"] = \"apple\"\n\nclass PumpkinPie(Pie):\n filling: Literal[\"pumpkin\"] = \"pumpkin\"\n\ndef get_discriminator_value(v: Any) -> str:\n if isinstance(v, dict):\n return v.get(\"fruit\", v.get(\"filling\"))\n return getattr(v, \"fruit\", getattr(v, \"filling\", None))\n\n# Using a simple type instead of Annotated[ApplePie, Tag(\"apple\")] to see if it's the nested Annotated\nDessert = Annotated[\n Union[ApplePie, PumpkinPie],\n Discriminator(get_discriminator_value),\n]\n\napp = FastAPI()\n@app.post(\"/dessert\")\nasync def dinner(dessert: Dessert) -> dict[str, Any]:\n return dessert.model_dump()\n\nclient = TestClient(app)\n\ndef test_repro():\n response = client.post(\"/dessert\", json={\"time_to_cook\": 10, \"num_ingredients\": 5, \"fruit\": \"apple\"})\n print(f\"Status code: {response.status_code}\")\n print(f\"Response: {response.json()}\")\n assert response.status_code == 200\n assert response.json() == {\"time_to_cook\": 10, \"num_ingredients\": 5, \"fruit\": \"apple\"}\n\nif __name__ == \"__main__\":\n try:\n test_repro()\n print(\"Success\")\n except Exception as e:\n import traceback\n traceback.print_exc()\n print(f\"Failure: {e}\")\nEOF\npython3 /tmp/repro.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recenfull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"/tmp/swe_work/eval_submission_nothink/fastapi_12942/a/tmp/repro.py\", line 28, in <module>\n @app.post(\"/dessert\")\n ~~~~~~~~^^^^^^^^^^^^\n File \"/workspace/fastapi/routing.py\", line 1070, in decorator\n self.add_api_route(\n ~~~~~~~~~~~~~~~~~~^\n path,\n ^^^^^\n ...<23 lines>...\n generate_unique_id_function=generate_unique_id_function,\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n )\n ^\n File \"/workspace/fastapi/routing.py\", line 1009, in add_api_route\n route = route_class(\n self.prefix + path,\n ...<24 lines>...\n generate_unique_id_function=current_generate_unique_id,\n )\n File \"/workspace/fastapi/routing.py\", line 630, in __init__\n self.dependant = get_dependant(path=self.path_format, call=self.endpoint)\n ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/workspace/fastapi/dependencies/utils.py\", line 293, in get_dependant\n param_details = analyze_param(\n param_name=param_name,\n ...<2 lines>...\n is_path_param=is_path_param,\n )\n File \"/workspace/fastapi/dependencies/utils.py\", line 496, in analyze_param\n field = create_model_field(\n name=param_name,\n ...<4 lines>...\n field_info=field_info,\n )\n File \"/workspace/fastapi/utils.py\", line 98, in create_model_field\n raise fastapi.exceptions.FastAPIError(\n ...<7 lines>...\n ) from None\nfastapi.exceptions.FastAPIError: Invalid args for response field! Hint: check that typing.Annotated[typing.Union[__main__.ApplePie, __main__.PumpkinPie], Discriminator(discriminator=<function get_discriminator_value at 0x7fc80592f7e0>, custom_error_type=None, custom_error_message=None, custom_error_context=None)] is a valid Pydantic field |
| 21 | run_command | {
"command": "cat <<EOF > /tmp/repro.py\nfrom typing import Any, Literal, Annotated, Union\nfrom pydantic import BaseModel, Discriminator\nfrom fastapi import FastAPI, Body\nfrom fastapi.testclient import TestClient\n\nclass Pie(BaseModel):\n time_to_cook: int\n num_ingredients: int\n\nclass ApplePie(Pie):\n fruit: Literal[\"apple\"] = \"apple\"\n\nclass PumpkinPie(Pie):\n filling: Literal[\"pumpkin\"] = \"pumpkin\"\n\ndef get_discriminator_value(v: Any) -> str:\n if isinstance(v, dict):\n return v.get(\"fruit\", v.get(\"filling\"))\n return getattr(v, \"fruit\", getattr(v, \"filling\", None))\n\n# Use a simple Pydantic model to see if it works\nclass DessertModel(BaseModel):\n dessert: Union[ApplePie, PumpkinPie]\n\napp = FastAPI()\n@app.post(\"/dessert\")\nasync def dinner(dessert: DessertModel) -> dict[str, Any]:\n return dessert.model_dump()\n\nclient = TestClient(app)\n\ndef test_repro():\n response = client.post(\"/dessert\", json={\"dessert\": {\"time_to_cook\": 10, \"num_ingredients\": 5, \"fruit\": \"apple\"}})\n print(f\"Status code: {response.status_code}\")\n print(f\"Response: {response.json()}\")\n assert response.status_code == 200\n\nif __name__ == \"__main__\":\n try:\n test_repro()\n print(\"Success\")\n except Exception as e:\n import traceback\n traceback.print_exc()\n print(f\"Failure: {e}\")\nEOF\npython3 /tmp/repro.py"
} | {"status": "ok", "stdout": "Status code: 200\nResponse: {'dessert': {'time_to_cook': 10, 'num_ingredients': 5, 'fruit': 'apple'}}\nSuccess\n", "exit_code": 0}full{"status": "ok", "stdout": "Status code: 200\nResponse: {'dessert': {'time_to_cook': 10, 'num_ingredients': 5, 'fruit': 'apple'}}\nSuccess\n", "exit_code": 0} |
| 22 | run_command | {
"command": "cat <<EOF > /tmp/repro.py\nfrom typing import Any, Literal, Annotated, Union\nfrom pydantic import BaseModel, Discriminator\nfrom fastapi import FastAPI, Body\nfrom fastapi.testclient import TestClient\n\nclass Pie(BaseModel):\n time_to_cook: int\n num_ingredients: int\n\nclass ApplePie(Pie):\n fruit: Literal[\"apple\"] = \"apple\"\n\nclass PumpkinPie(Pie):\n filling: Literal[\"pumpkin\"] = \"pumpkin\"\n\ndef get_discriminator_value(v: Any) -> str:\n if isinstance(v, dict):\n return v.get(\"fruit\", v.get(\"filling\"))\n return getattr(v, \"fruit\", getattr(v, \"filling\", None))\n\n# The issue is that when using Annotated[Union[...], Discriminator(...)], \n# FastAPI doesn't recognize it as a body parameter.\nDessert = Annotated[\n Union[ApplePie, PumpkinPie],\n Discriminator(get_discriminator_value),\n]\n\napp = FastAPI()\n@app.post(\"/dessert\")\nasync def dinner(dessert: Dessert) -> dict[str, Any]:\n return dessert.model_dump()\n\nclient = TestClient(app)\n\ndef test_repro():\n # If it's recognized as a body parameter, this should work.\n # If it's recognized as a query parameter, it will fail with 422.\n response = client.post(\"/dessert\", json={\"time_to_cook\": 10, \"num_ingredients\": 5, \"fruit\": \"apple\"})\n print(f\"Status code: {response.status_code}\")\n print(f\"Response: {response.json()}\")\n assert response.status_code == 200\n\nif __name__ == \"__main__\":\n try:\n test_repro()\n print(\"Success\")\n except Exception as e:\n import traceback\n traceback.print_exc()\n print(f\"Failure: {e}\")\nEOF\npython3 /tmp/repro.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recenfull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"/tmp/swe_work/eval_submission_nothink/fastapi_12942/a/tmp/repro.py\", line 29, in <module>\n @app.post(\"/dessert\")\n ~~~~~~~~^^^^^^^^^^^^\n File \"/workspace/fastapi/routing.py\", line 1070, in decorator\n self.add_api_route(\n ~~~~~~~~~~~~~~~~~~^\n path,\n ^^^^^\n ...<23 lines>...\n generate_unique_id_function=generate_unique_id_function,\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n )\n ^\n File \"/workspace/fastapi/routing.py\", line 1009, in add_api_route\n route = route_class(\n self.prefix + path,\n ...<24 lines>...\n generate_unique_id_function=current_generate_unique_id,\n )\n File \"/workspace/fastapi/routing.py\", line 630, in __init__\n self.dependant = get_dependant(path=self.path_format, call=self.endpoint)\n ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/workspace/fastapi/dependencies/utils.py\", line 293, in get_dependant\n param_details = analyze_param(\n param_name=param_name,\n ...<2 lines>...\n is_path_param=is_path_param,\n )\n File \"/workspace/fastapi/dependencies/utils.py\", line 496, in analyze_param\n field = create_model_field(\n name=param_name,\n ...<4 lines>...\n field_info=field_info,\n )\n File \"/workspace/fastapi/utils.py\", line 98, in create_model_field\n raise fastapi.exceptions.FastAPIError(\n ...<7 lines>...\n ) from None\nfastapi.exceptions.FastAPIError: Invalid args for response field! Hint: check that typing.Annotated[typing.Union[__main__.ApplePie, __main__.PumpkinPie], Discriminator(discriminator=<function get_discriminator_value at 0x7c825b979620>, custom_error_type=None, custom_error_message=None, custom_error_context=None)] is a valid Pydantic field |
| 23 | read_file | {
"end_line": 460,
"filepath": "fastapi/dependencies/utils.py",
"start_line": 360
} | {"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " value: Any,\n is_path_param: bool,\n) -> ParamDetails:\n field_info = None\n depends full{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " value: Any,\n is_path_param: bool,\n) -> ParamDetails:\n field_info = None\n depends = None\n type_annotation: Any = Any\n use_annotation: Any = Any\n if annotation is not inspect.Signature.empty:\n use_annotation = annotation\n type_annotation = annotation\n # Extract Annotated info\n if get_origin(use_annotation) is Annotated:\n annotated_args = get_args(annotation)\n type_annotation = annotated_args[0]\n fastapi_annotations = [\n arg\n for arg in annotated_args[1:]\n if isinstance(arg, (FieldInfo, params.Depends))\n ]\n fastapi_specific_annotations = [\n arg\n for arg in fastapi_annotations\n if isinstance(arg, (params.Param, params.Body, params.Depends))\n ]\n if fastapi_specific_annotations:\n fastapi_annotation: Union[FieldInfo, params.Depends, None] = (\n fastapi_specific_annotations[-1]\n )\n else:\n fastapi_annotation = None\n # Set default for Annotated FieldInfo\n if isinstance(fastapi_annotation, FieldInfo):\n # Copy `field_info` because we mutate `field_info.default` below.\n field_info = copy_field_info(\n field_info=fastapi_annotation, annotation=use_annotation\n )\n assert (\n field_info.default is Undefined or field_info.default is RequiredParam\n ), (\n f\"`{field_info.__class__.__name__}` default value cannot be set in\"\n f\" `Annotated` for {param_name!r}. Set the default value with `=` instead.\"\n )\n if value is not inspect.Signature.empty:\n assert not is_path_param, \"Path parameters cannot have default values\"\n field_info.default = value\n else:\n field_ |
| 24 | run_command | {
"command": "cat <<EOF > /tmp/repro.py\nfrom typing import Any, Literal, Annotated, Union\nfrom pydantic import BaseModel, Discriminator\nfrom fastapi import FastAPI, Body\nfrom fastapi.testclient import TestClient\n\nclass Pie(BaseModel):\n time_to_cook: int\n num_ingredients: int\n\nclass ApplePie(Pie):\n fruit: Literal[\"apple\"] = \"apple\"\n\nclass PumpkinPie(Pie):\n filling: Literal[\"pumpkin\"] = \"pumpkin\"\n\ndef get_discriminator_value(v: Any) -> str:\n if isinstance(v, dict):\n return v.get(\"fruit\", v.get(\"filling\"))\n return getattr(v, \"fruit\", getattr(v, \"filling\", None))\n\n# The issue is that when using Annotated[Union[...], Discriminator(...)], \n# FastAPI doesn't recognize it as a body parameter.\nDessert = Annotated[\n Union[ApplePie, PumpkinPie],\n Discriminator(get_discriminator_value),\n]\n\napp = FastAPI()\n@app.post(\"/dessert\")\nasync def dinner(dessert: Annotated[Dessert, Body()]) -> dict[str, Any]:\n return dessert.model_dump()\n\nclient = TestClient(app)\n\ndef test_repro():\n response = client.post(\"/dessert\", json={\"time_to_cook\": 10, \"num_ingredients\": 5, \"fruit\": \"apple\"})\n print(f\"Status code: {response.status_code}\")\n print(f\"Response: {response.json()}\")\n assert response.status_code == 200\n\nif __name__ == \"__main__\":\n try:\n test_repro()\n print(\"Success\")\n except Exception as e:\n import traceback\n traceback.print_exc()\n print(f\"Failure: {e}\")\nEOF\npython3 /tmp/repro.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recenfull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"/tmp/swe_work/eval_submission_nothink/fastapi_12942/a/tmp/repro.py\", line 29, in <module>\n @app.post(\"/dessert\")\n ~~~~~~~~^^^^^^^^^^^^\n File \"/workspace/fastapi/routing.py\", line 1070, in decorator\n self.add_api_route(\n ~~~~~~~~~~~~~~~~~~^\n path,\n ^^^^^\n ...<23 lines>...\n generate_unique_id_function=generate_unique_id_function,\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n )\n ^\n File \"/workspace/fastapi/routing.py\", line 1009, in add_api_route\n route = route_class(\n self.prefix + path,\n ...<24 lines>...\n generate_unique_id_function=current_generate_unique_id,\n )\n File \"/workspace/fastapi/routing.py\", line 630, in __init__\n self.dependant = get_dependant(path=self.path_format, call=self.endpoint)\n ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/workspace/fastapi/dependencies/utils.py\", line 293, in get_dependant\n param_details = analyze_param(\n param_name=param_name,\n ...<2 lines>...\n is_path_param=is_path_param,\n )\n File \"/workspace/fastapi/dependencies/utils.py\", line 496, in analyze_param\n field = create_model_field(\n name=param_name,\n ...<4 lines>...\n field_info=field_info,\n )\n File \"/workspace/fastapi/utils.py\", line 98, in create_model_field\n raise fastapi.exceptions.FastAPIError(\n ...<7 lines>...\n ) from None\nfastapi.exceptions.FastAPIError: Invalid args for response field! Hint: check that typing.Annotated[typing.Union[__main__.ApplePie, __main__.PumpkinPie], Discriminator(discriminator=<function get_discriminator_value at 0x7c95ed72d620>, custom_error_type=None, custom_error_message=None, custom_error_context=None), Body(PydanticUndefined)] i |