← eval3_submission_v2t

fastapi_13207

resolved RESOLVED PASS · 19 tool calls · 72 s · fastapi/fastapi

Task input

🐛 Fix OpenAPI schema support for computed fields when using `separate_input_output_schemas=False`

This demonstrates how the output schema differs when the FastAPI app runs with different values for `separate_input_output_schemas` - namely, the computed fields are omitted.

Initially reported here: https://github.com/fastapi/fastapi/discussions/10342

Tool calls (20)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"separate_input_output_schemas\" ."
}
{"status": "ok", "stdout": "./fastapi/_compat/may_v1.py:65: separate_input_output_schemas: bool = True,\n./fastapi/_compat/v1.py:243: separate_input_output_sche
full
{"status": "ok", "stdout": "./fastapi/_compat/may_v1.py:65:        separate_input_output_schemas: bool = True,\n./fastapi/_compat/v1.py:243:    separate_input_output_schemas: bool = True,\n./fastapi/_compat/v1.py:259:    separate_input_output_schemas: bool = True,\n./fastapi/_compat/main.py:271:    separate_input_output_schemas: bool = True,\n./fastapi/_compat/main.py:284:            separate_input_output_schemas=separate_input_output_schemas,\n./fastapi/_compat/main.py:295:                separate_input_output_schemas=separate_input_output_schemas,\n./fastapi/_compat/main.py:309:            separate_input_output_schemas=separate_input_output_schemas,\n./fastapi/_compat/main.py:322:    separate_input_output_schemas: bool = True,\n./fastapi/_compat/main.py:331:            separate_input_output_schemas=separate_input_output_schemas,\n./fastapi/_compat/main.py:341:            separate_input_output_schemas=separate_input_output_schemas,\n./fastapi/_compat/v2.py:181:    separate_input_output_schemas: bool = True,\n./fastapi/_compat/v2.py:184:        None if separate_input_output_schemas else \"validation\"\n./fastapi/_compat/v2.py:201:    separate_input_output_schemas: bool = True,\n./fastapi/_compat/v2.py:208:        None if separate_input_output_schemas else \"validation\"\n./fastapi/applications.py:791:        separate_input_output_schemas: Annotated[\n./fastapi/applications.py:875:        self.separate_input_output_schemas = separate_input_output_schemas\n./fastapi/applications.py:1078:                separate_input_output_schemas=self.separate_input_output_schemas,\n./fastapi/openapi/utils.py:102:    separate_input_output_schemas: bool = True,\n./fastapi/openapi/utils.py:133:                separate_input_output_schemas=separate_input_output_schemas,\n./fastapi/openapi/utils.py:175:    separate_input_output_schemas: bool = True,\n./fastapi/openapi/utils.py:184:        separate_input_output_schemas=separate_input_output_schemas,\n./fastapi/openapi/utils.py:258:    se
2run_command
{
 "command": "cat > /tmp/reproduce_issue.py <<'EOF'\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\nfrom fastapi.testclient import TestClient\nimport json\n\nclass Item(BaseModel):\n    name: str\n    price: float\n\n    @computed_field\n    @property\n    def total(self) -> float:\n        return self.price * 1.1\n\napp = FastAPI(separate_input_output_schemas=False)\n\n@app.get(\"/item\", response_model=Item)\ndef get_item():\n    return Item(name=\"test\", price=10.0)\n\nclient = TestClient(app)\n\ndef test_computed_field_in_openapi():\n    response = client.get(\"/openapi.json\")\n    openapi_schema = response.json()\n    \n    # Look for the Item schema in components/schemas\n    schemas = openapi_schema.get(\"components\", {}).get(\"schemas\", {})\n    item_schema = schemas.get(\"Item\", {})\n    \n    properties = item_schema.get(\"properties\", {})\n    \n    print(f\"Properties found: {list(properties.keys())}\")\n    assert \"total\" in properties, \"Computed field 'total' should be in the OpenAPI schema when separate_input_output_schemas=False\"\n\nif __name__ == \"__main__\":\n    try:\n        test_computed_field_in_openapi()\n        print(\"Success: Computed field found!\")\n    except AssertionError as e:\n        print(f\"Failure: {e}\")\nEOF\npython3 /tmp/reproduce_issue.py"
}
{"status": "ok", "stdout": "Properties found: ['name', 'price']\nFailure: Computed field 'total' should be in the OpenAPI schema when separate_input_output_sche
full
{"status": "ok", "stdout": "Properties found: ['name', 'price']\nFailure: Computed field 'total' should be in the OpenAPI schema when separate_input_output_schemas=False\n", "exit_code": 0}
3read_file
{
 "filepath": "fastapi/openapi/utils.py",
 "start_line": 100,
 "end_line": 200
}
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n ],\n sepa
full
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "        Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n    ],\n    separate_input_output_schemas: bool = True,\n) -> List[Dict[str, Any]]:\n    parameters = []\n    flat_dependant = get_flat_dependant(dependant, skip_repeats=True)\n    path_params = _get_flat_fields_from_params(flat_dependant.path_params)\n    query_params = _get_flat_fields_from_params(flat_dependant.query_params)\n    header_params = _get_flat_fields_from_params(flat_dependant.header_params)\n    cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params)\n    parameter_groups = [\n        (ParamTypes.path, path_params),\n        (ParamTypes.query, query_params),\n        (ParamTypes.header, header_params),\n        (ParamTypes.cookie, cookie_params),\n    ]\n    default_convert_underscores = True\n    if len(flat_dependant.header_params) == 1:\n        first_field = flat_dependant.header_params[0]\n        if lenient_issubclass(first_field.type_, BaseModel):\n            default_convert_underscores = getattr(\n                first_field.field_info, \"convert_underscores\", True\n            )\n    for param_type, param_group in parameter_groups:\n        for param in param_group:\n            field_info = param.field_info\n            # field_info = cast(Param, field_info)\n            if not getattr(field_info, \"include_in_schema\", True):\n                continue\n            param_schema = get_schema_from_model_field(\n                field=param,\n                model_name_map=model_name_map,\n                field_mapping=field_mapping,\n                separate_input_output_schemas=separate_input_output_schemas,\n            )\n            name = param.alias\n            convert_underscores = getattr(\n                param.field_info,\n                \"convert_underscores\",\n                default_convert_underscores,\n            )\n            if (\n                
4read_file
{
 "end_line": 350,
 "filepath": "fastapi/openapi/utils.py",
 "start_line": 250
}
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "def get_openapi_path(\n *,\n route: routing.APIRoute,\n operation_ids: Set[str],\n model_na
full
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "def get_openapi_path(\n    *,\n    route: routing.APIRoute,\n    operation_ids: Set[str],\n    model_name_map: ModelNameMap,\n    field_mapping: Dict[\n        Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n    ],\n    separate_input_output_schemas: bool = True,\n) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]:\n    path = {}\n    security_schemes: Dict[str, Any] = {}\n    definitions: Dict[str, Any] = {}\n    assert route.methods is not None, \"Methods must be a list\"\n    if isinstance(route.response_class, DefaultPlaceholder):\n        current_response_class: Type[Response] = route.response_class.value\n    else:\n        current_response_class = route.response_class\n    assert current_response_class, \"A response class is needed to generate OpenAPI\"\n    route_response_media_type: Optional[str] = current_response_class.media_type\n    if route.include_in_schema:\n        for method in route.methods:\n            operation = get_openapi_operation_metadata(\n                route=route, method=method, operation_ids=operation_ids\n            )\n            parameters: List[Dict[str, Any]] = []\n            flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True)\n            security_definitions, operation_security = get_openapi_security_definitions(\n                flat_dependant=flat_dependant\n            )\n            if operation_security:\n                operation.setdefault(\"security\", []).extend(operation_security)\n            if security_definitions:\n                security_schemes.update(security_definitions)\n            operation_parameters = _get_openapi_operation_parameters(\n                dependant=route.dependant,\n                model_name_map=model_name_map,\n                field_mapping=field_mapping,\n                separate_input_output_schemas=separate_input_output_schemas,\n            )\n           
5read_file
{
 "end_line": 558,
 "filepath": "fastapi/openapi/utils.py",
 "start_line": 351
}
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " if route.response_field:\n response_schema = get_schema_from_model_field(\n field=route.re
full
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "                    if route.response_field:\n                        response_schema = get_schema_from_model_field(\n                            field=route.response_field,\n                            model_name_map=model_name_map,\n                            field_mapping=field_mapping,\n                            separate_input_output_schemas=separate_input_output_schemas,\n                        )\n                    else:\n                        response_schema = {}\n                operation.setdefault(\"responses\", {}).setdefault(\n                    status_code, {}\n                ).setdefault(\"content\", {}).setdefault(route_response_media_type, {})[\n                    \"schema\"\n                ] = response_schema\n            if route.responses:\n                operation_responses = operation.setdefault(\"responses\", {})\n                for (\n                    additional_status_code,\n                    additional_response,\n                ) in route.responses.items():\n                    process_response = additional_response.copy()\n                    process_response.pop(\"model\", None)\n                    status_code_key = str(additional_status_code).upper()\n                    if status_code_key == \"DEFAULT\":\n                        status_code_key = \"default\"\n                    openapi_response = operation_responses.setdefault(\n                        status_code_key, {}\n                    )\n                    assert isinstance(process_response, dict), (\n                        \"An additional response must be a dict\"\n                    )\n                    field = route.response_fields.get(additional_status_code)\n                    additional_field_schema: Optional[Dict[str, Any]] = None\n                    if field:\n                        additional_field_schema = get_schema_from_model_field(\n                            field=fiel
6read_file
{
 "end_line": 558,
 "filepath": "fastapi/openapi/utils.py",
 "start_line": 501
}
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " operation_ids: Set[str] = set()\n all_fields = get_fields_from_routes(list(routes or []) +
full
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "    operation_ids: Set[str] = set()\n    all_fields = get_fields_from_routes(list(routes or []) + list(webhooks or []))\n    model_name_map = get_compat_model_name_map(all_fields)\n    field_mapping, definitions = get_definitions(\n        fields=all_fields,\n        model_name_map=model_name_map,\n        separate_input_output_schemas=separate_input_output_schemas,\n    )\n    for route in routes or []:\n        if isinstance(route, routing.APIRoute):\n            result = get_openapi_path(\n                route=route,\n                operation_ids=operation_ids,\n                model_name_map=model_name_map,\n                field_mapping=field_mapping,\n                separate_input_output_schemas=separate_input_output_schemas,\n            )\n            if result:\n                path, security_schemes, path_definitions = result\n                if path:\n                    paths.setdefault(route.path_format, {}).update(path)\n                if security_schemes:\n                    components.setdefault(\"securitySchemes\", {}).update(\n                        security_schemes\n                    )\n                if path_definitions:\n                    definitions.update(path_definitions)\n    for webhook in webhooks or []:\n        if isinstance(webhook, routing.APIRoute):\n            result = get_openapi_path(\n                route=webhook,\n                operation_ids=operation_ids,\n                model_name_map=model_name_map,\n                field_mapping=field_mapping,\n                separate_input_output_schemas=separate_input_output_schemas,\n            )\n            if result:\n                path, security_schemes, path_definitions = result\n                if path:\n                    webhook_paths.setdefault(webhook.path_format, {}).update(path)\n                if security_schemes:\n                    components.setdefault(\"securitySchemes\", {}).update
7search_similar_code
{
 "query": "get_definitions"
}
{"status": "error", "error_type": "EmbeddingsUnavailable", "error_message": "No embeddings"}
full
{"status": "error", "error_type": "EmbeddingsUnavailable", "error_message": "No embeddings"}
8read_file
{
 "end_line": 249,
 "filepath": "fastapi/openapi/utils.py",
 "start_line": 1
}
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "import http.client\nimport inspect\nimport warnings\nfrom typing import Any, Dict, List, Op
full
{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "import http.client\nimport inspect\nimport warnings\nfrom typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union, cast\n\nfrom fastapi import routing\nfrom fastapi._compat import (\n    JsonSchemaValue,\n    ModelField,\n    Undefined,\n    get_compat_model_name_map,\n    get_definitions,\n    get_schema_from_model_field,\n    lenient_issubclass,\n)\nfrom fastapi.datastructures import DefaultPlaceholder\nfrom fastapi.dependencies.models import Dependant\nfrom fastapi.dependencies.utils import (\n    _get_flat_fields_from_params,\n    get_flat_dependant,\n    get_flat_params,\n)\nfrom fastapi.encoders import jsonable_encoder\nfrom fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX\nfrom fastapi.openapi.models import OpenAPI\nfrom fastapi.params import Body, ParamTypes\nfrom fastapi.responses import Response\nfrom fastapi.types import ModelNameMap\nfrom fastapi.utils import (\n    deep_dict_update,\n    generate_operation_id_for_path,\n    is_body_allowed_for_status_code,\n)\nfrom pydantic import BaseModel\nfrom starlette.responses import JSONResponse\nfrom starlette.routing import BaseRoute\nfrom typing_extensions import Literal\n\nfrom .._compat import _is_model_field\n\nvalidation_error_definition = {\n    \"title\": \"ValidationError\",\n    \"type\": \"object\",\n    \"properties\": {\n        \"loc\": {\n            \"title\": \"Location\",\n            \"type\": \"array\",\n            \"items\": {\"anyOf\": [{\"type\": \"string\"}, {\"type\": \"integer\"}]},\n        },\n        \"msg\": {\"title\": \"Message\", \"type\": \"string\"},\n        \"type\": {\"title\": \"Error Type\", \"type\": \"string\"},\n    },\n    \"required\": [\"loc\", \"msg\", \"type\"],\n}\n\nvalidation_error_response_definition = {\n    \"title\": \"HTTPValidationError\",\n    \"type\": \"object\",\n    \"properties\": {\n        \"detail\": {\n            \"title\": \"Detail\",\n       
9read_file
{
 "end_line": 350,
 "filepath": "fastapi/_compat/main.py",
 "start_line": 200
}
{"status": "ok", "filepath": "fastapi/_compat/main.py", "content": "def is_sequence_field(field: ModelField) -> bool:\n if isinstance(field, may_v1.ModelField):
full
{"status": "ok", "filepath": "fastapi/_compat/main.py", "content": "def is_sequence_field(field: ModelField) -> bool:\n    if isinstance(field, may_v1.ModelField):\n        from fastapi._compat import v1\n\n        return v1.is_sequence_field(field)\n    else:\n        assert PYDANTIC_V2\n        from . import v2\n\n        return v2.is_sequence_field(field)  # type: ignore[arg-type]\n\n\ndef serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:\n    if isinstance(field, may_v1.ModelField):\n        from fastapi._compat import v1\n\n        return v1.serialize_sequence_value(field=field, value=value)\n    else:\n        assert PYDANTIC_V2\n        from . import v2\n\n        return v2.serialize_sequence_value(field=field, value=value)  # type: ignore[arg-type]\n\n\ndef _model_rebuild(model: Type[BaseModel]) -> None:\n    if lenient_issubclass(model, may_v1.BaseModel):\n        from fastapi._compat import v1\n\n        v1._model_rebuild(model)\n    elif PYDANTIC_V2:\n        from . import v2\n\n        v2._model_rebuild(model)\n\n\ndef get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap:\n    v1_model_fields = [\n        field for field in fields if isinstance(field, may_v1.ModelField)\n    ]\n    if v1_model_fields:\n        from fastapi._compat import v1\n\n        v1_flat_models = v1.get_flat_models_from_fields(\n            v1_model_fields, known_models=set()\n        )\n        all_flat_models = v1_flat_models\n    else:\n        all_flat_models = set()\n    if PYDANTIC_V2:\n        from . import v2\n\n        v2_model_fields = [\n            field for field in fields if isinstance(field, v2.ModelField)\n        ]\n        v2_flat_models = v2.get_flat_models_from_fields(\n            v2_model_fields, known_models=set()\n        )\n        all_flat_models = all_flat_models.union(v2_flat_models)\n\n        model_name_map = v2.get_model_name_map(all_flat_models)\n        return model_name_map\n    from fastapi._compat import v
10read_file
{
 "end_line": 250,
 "filepath": "fastapi/_compat/v2.py",
 "start_line": 150
}
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": " # ModelField to its JSON Schema.\n return id(self)\n\n\ndef get_annotation_from_field_info(\n
full
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "        # ModelField to its JSON Schema.\n        return id(self)\n\n\ndef get_annotation_from_field_info(\n    annotation: Any, field_info: FieldInfo, field_name: str\n) -> Any:\n    return annotation\n\n\ndef _model_rebuild(model: Type[BaseModel]) -> None:\n    model.model_rebuild()\n\n\ndef _model_dump(\n    model: BaseModel, mode: Literal[\"json\", \"python\"] = \"json\", **kwargs: Any\n) -> Any:\n    return model.model_dump(mode=mode, **kwargs)\n\n\ndef _get_model_config(model: BaseModel) -> Any:\n    return model.model_config\n\n\ndef get_schema_from_model_field(\n    *,\n    field: ModelField,\n    model_name_map: ModelNameMap,\n    field_mapping: Dict[\n        Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n    ],\n    separate_input_output_schemas: bool = True,\n) -> Dict[str, Any]:\n    override_mode: Union[Literal[\"validation\"], None] = (\n        None if separate_input_output_schemas else \"validation\"\n    )\n    # This expects that GenerateJsonSchema was already used to generate the definitions\n    json_schema = field_mapping[(field, override_mode or field.mode)]\n    if \"$ref\" not in json_schema:\n        # TODO remove when deprecating Pydantic v1\n        # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207\n        json_schema[\"title\"] = field.field_info.title or field.alias.title().replace(\n            \"_\", \" \"\n        )\n    return json_schema\n\n\ndef get_definitions(\n    *,\n    fields: Sequence[ModelField],\n    model_name_map: ModelNameMap,\n    separate_input_output_schemas: bool = True,\n) -> Tuple[\n    Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue],\n    Dict[str, Dict[str, Any]],\n]:\n    schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)\n    override_mode: Union[Literal[\"validation\"], None] = (\n        None if 
11read_file
{
 "end_line": 486,
 "filepath": "fastapi/_compat/v2.py",
 "start_line": 251
}
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": " definitions=definitions, # type: ignore[arg-type]\n field_mapping=field_mapping,\n )\n return
full
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "        definitions=definitions,  # type: ignore[arg-type]\n        field_mapping=field_mapping,\n    )\n    return new_mapping, new_definitions\n\n\ndef _replace_refs(\n    *,\n    schema: Dict[str, Any],\n    old_name_to_new_name_map: Dict[str, str],\n) -> Dict[str, Any]:\n    new_schema = deepcopy(schema)\n    for key, value in new_schema.items():\n        if key == \"$ref\":\n            value = schema[\"$ref\"]\n            if isinstance(value, str):\n                ref_name = schema[\"$ref\"].split(\"/\")[-1]\n                if ref_name in old_name_to_new_name_map:\n                    new_name = old_name_to_new_name_map[ref_name]\n                    new_schema[\"$ref\"] = REF_TEMPLATE.format(model=new_name)\n            continue\n        if isinstance(value, dict):\n            new_schema[key] = _replace_refs(\n                schema=value,\n                old_name_to_new_name_map=old_name_to_new_name_map,\n            )\n        elif isinstance(value, list):\n            new_value = []\n            for item in value:\n                if isinstance(item, dict):\n                    new_item = _replace_refs(\n                        schema=item,\n                        old_name_to_new_name_map=old_name_to_new_name_map,\n                    )\n                    new_value.append(new_item)\n\n                else:\n                    new_value.append(item)\n            new_schema[key] = new_value\n    return new_schema\n\n\ndef _remap_definitions_and_field_mappings(\n    *,\n    model_name_map: ModelNameMap,\n    definitions: Dict[str, Any],\n    field_mapping: Dict[\n        Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n    ],\n) -> Tuple[\n    Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue],\n    Dict[str, Any],\n]:\n    old_name_to_new_name_map = {}\n    for field_key, schema in field_mapping.items():\n        model =
12read_file
{
 "end_line": 180,
 "filepath": "fastapi/_compat/v2.py",
 "start_line": 1
}
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "import re\nimport warnings\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass
full
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "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 ErrorWr
13edit_file
{
 "filepath": "fastapi/_compat/v2.py",
 "new_string": "    override_mode: Union[Literal[\"validation\"], None] = (\n        None if separate_input_output_schemas else \"serialization\"\n    )",
 "old_string": "    override_mode: Union[Literal[\"validation\"], None] = (\n        None if separate_input_output_schemas else \"validation\"\n    )"
}
{"status": "error", "error_type": "FileEditError", "error_message": "old_string matched 2 occurrences; provide more context or set allow_multiple"}
full
{"status": "error", "error_type": "FileEditError", "error_message": "old_string matched 2 occurrences; provide more context or set allow_multiple"}
14edit_file
{
 "filepath": "fastapi/_compat/v2.py",
 "new_string": "def get_schema_from_model_field(\n    *,\n    field: ModelField,\n    model_name_map: ModelNameMap,\n    field_mapping: Dict[\n        Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n    ],\n    separate_input_output_schemas: bool = True,\n) -> Dict[str, Any]:\n    override_mode: Union[Literal[\"validation\"], None] = (\n        None if separate_input_output_schemas else \"serialization\"\n    )\n    # This expects that GenerateJsonSchema was already used to generate the definitions",
 "old_string": "def get_schema_from_model_field(\n    *,\n    field: ModelField,\n    model_name_map: ModelNameMap,\n    field_mapping: Dict[\n        Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n    ],\n    separate_input_output_schemas: bool = True,\n) -> Dict[str, Any]:\n    override_mode: Union[Literal[\"validation\"], None] = (\n        None if separate_input_output_schemas else \"validation\"\n    )\n    # This expects that GenerateJsonSchema was already used to generate the definitions"
}
{"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@@ -181,7 +181,7 @@\n     separate_input_output_schemas: bool = True,\n ) -> Dict[str, Any]:\n     override_mode: Union[Literal[\"validation\"], None] = (\n-        None if separate_input_output_schemas else \"validation\"\n+        None if separate_input_output_schemas else \"serialization\"\n     )\n     # This expects that GenerateJsonSchema was already used to generate the definitions\n     json_schema = field_mapping[(field, override_mode or field.mode)]\n", "is_truncated": false}
15run_command
{
 "command": "python3 /tmp/reproduce_issue.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recen
full
{"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/eval3_submission_v2t/fastapi_13207/a/tmp/reproduce_issue.py\", line 38, in <module>\n    test_computed_field_in_openapi()\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^\n  File \"/tmp/swe_work/eval3_submission_v2t/fastapi_13207/a/tmp/reproduce_issue.py\", line 24, in test_computed_field_in_openapi\n    response = client.get(\"/openapi.json\")\n  File \"/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py\", line 473, in get\n    return super().get(\n           ~~~~~~~~~~~^\n        url,\n        ^^^^\n    ...<6 lines>...\n        extensions=extensions,\n        ^^^^^^^^^^^^^^^^^^^^^^\n    )\n    ^\n  File \"/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/httpx/_client.py\", line 1053, in get\n    return self.request(\n           ~~~~~~~~~~~~^\n        \"GET\",\n        ^^^^^^\n    ...<7 lines>...\n        extensions=extensions,\n        ^^^^^^^^^^^^^^^^^^^^^^\n    )\n    ^\n  File \"/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py\", line 445, in request\n    return super().request(\n           ~~~~~~~~~~~~~~~^\n        method,\n        ^^^^^^^\n    ...<11 lines>...\n        extensions=extensions,\n        ^^^^^^^^^^^^^^^^^^^^^^\n    )\n    ^\n  File \"/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/httpx/_client.py\", line 825, in request\n    return self.send(request, auth=auth, follow_redirects=follow_redirects)\n           ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/httpx/_client.py\", line 914, in send\n    response = self._send_handling_auth(\n        request,\n    ...<2 lines>...\n        history=[],\n    )\n  File \"/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/httpx/_client.py\", line 942, in _send_handling_auth
16run_command
{
 "command": "tail -n 100 /tmp/reproduce_issue.py"
}
{"status": "ok", "stdout": "from fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\nfrom fastapi.testclient import TestClient\nimport json\
full
{"status": "ok", "stdout": "from fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\nfrom fastapi.testclient import TestClient\nimport json\n\nclass Item(BaseModel):\n    name: str\n    price: float\n\n    @computed_field\n    @property\n    def total(self) -> float:\n        return self.price * 1.1\n\napp = FastAPI(separate_input_output_schemas=False)\n\n@app.get(\"/item\", response_model=Item)\ndef get_item():\n    return Item(name=\"test\", price=10.0)\n\nclient = TestClient(app)\n\ndef test_computed_field_in_openapi():\n    response = client.get(\"/openapi.json\")\n    openapi_schema = response.json()\n    \n    # Look for the Item schema in components/schemas\n    schemas = openapi_schema.get(\"components\", {}).get(\"schemas\", {})\n    item_schema = schemas.get(\"Item\", {})\n    \n    properties = item_schema.get(\"properties\", {})\n    \n    print(f\"Properties found: {list(properties.keys())}\")\n    assert \"total\" in properties, \"Computed field 'total' should be in the OpenAPI schema when separate_input_output_schemas=False\"\n\nif __name__ == \"__main__\":\n    try:\n        test_computed_field_in_openapi()\n        print(\"Success: Computed field found!\")\n    except AssertionError as e:\n        print(f\"Failure: {e}\")\n", "exit_code": 0}
17read_file
{
 "end_line": 230,
 "filepath": "fastapi/_compat/v2.py",
 "start_line": 180
}
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": " ],\n separate_input_output_schemas: bool = True,\n) -> Dict[str, Any]:\n override_mode: Union
full
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "    ],\n    separate_input_output_schemas: bool = True,\n) -> Dict[str, Any]:\n    override_mode: Union[Literal[\"validation\"], None] = (\n        None if separate_input_output_schemas else \"serialization\"\n    )\n    # This expects that GenerateJsonSchema was already used to generate the definitions\n    json_schema = field_mapping[(field, override_mode or field.mode)]\n    if \"$ref\" not in json_schema:\n        # TODO remove when deprecating Pydantic v1\n        # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207\n        json_schema[\"title\"] = field.field_info.title or field.alias.title().replace(\n            \"_\", \" \"\n        )\n    return json_schema\n\n\ndef get_definitions(\n    *,\n    fields: Sequence[ModelField],\n    model_name_map: ModelNameMap,\n    separate_input_output_schemas: bool = True,\n) -> Tuple[\n    Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue],\n    Dict[str, Dict[str, Any]],\n]:\n    schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)\n    override_mode: Union[Literal[\"validation\"], None] = (\n        None if separate_input_output_schemas else \"validation\"\n    )\n    validation_fields = [field for field in fields if field.mode == \"validation\"]\n    serialization_fields = [field for field in fields if field.mode == \"serialization\"]\n    flat_validation_models = get_flat_models_from_fields(\n        validation_fields, known_models=set()\n    )\n    flat_serialization_models = get_flat_models_from_fields(\n        serialization_fields, known_models=set()\n    )\n    flat_validation_model_fields = [\n        ModelField(\n            field_info=FieldInfo(annotation=model),\n            name=model.__name__,\n            mode=\"validation\",\n        )\n        for model in flat_validation_models\n    ]\n    flat_serialization_model_fields = [\n     
18edit_file
{
 "filepath": "fastapi/_compat/v2.py",
 "new_string": "def get_definitions(\n    *,\n    fields: Sequence[ModelField],\n    model_name_map: ModelNameMap,\n    separate_input_output_schemas: bool = True,\n) -> Tuple[\n    Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue],\n    Dict[str, Dict[str, Any]],\n]:\n    schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)\n    override_mode: Union[Literal[\"serialization\"], None] = (\n        None if separate_input_output_schemas else \"serialization\"\n    )\n    validation_fields = [field for field in fields if field.mode == \"validation\"]",
 "old_string": "def get_definitions(\n    *,\n    fields: Sequence[ModelField],\n    model_name_map: ModelNameMap,\n    separate_input_output_schemas: bool = True,\n) -> Tuple[\n    Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue],\n    Dict[str, Dict[str, Any]],\n]:\n    schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)\n    override_mode: Union[Literal[\"validation\"], None] = (\n        None if separate_input_output_schemas else \"validation\"\n    )\n    validation_fields = [field for field in fields if field.mode == \"validation\"]"
}
{"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@@ -204,8 +204,8 @@\n     Dict[str, Dict[str, Any]],\n ]:\n     schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)\n-    override_mode: Union[Literal[\"validation\"], None] = (\n-        None if separate_input_output_schemas else \"validation\"\n+    override_mode: Union[Literal[\"serialization\"], None] = (\n+        None if separate_input_output_schemas else \"serialization\"\n     )\n     validation_fields = [field for field in fields if field.mode == \"validation\"]\n     serialization_fields = [field for field in fields if field.mode == \"serialization\"]\n", "is_truncated": false}
19run_command
{
 "command": "python3 /tmp/reproduce_issue.py"
}
{"status": "ok", "stdout": "Properties found: ['name', 'price', 'total']\nSuccess: Computed field found!\n", "exit_code": 0}
full
{"status": "ok", "stdout": "Properties found: ['name', 'price', 'total']\nSuccess: Computed field found!\n", "exit_code": 0}
20submit_patch
[]
{"status": "ok", "patch_size": 1210, "files_changed": 1}
full
{"status": "ok", "patch_size": 1210, "files_changed": 1}

Patch

diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py
index 3d91814c..ae5cabe3 100644
--- a/fastapi/_compat/v2.py
+++ b/fastapi/_compat/v2.py
@@ -181,7 +181,7 @@ def get_schema_from_model_field(
     separate_input_output_schemas: bool = True,
 ) -> Dict[str, Any]:
     override_mode: Union[Literal["validation"], None] = (
-        None if separate_input_output_schemas else "validation"
+        None if separate_input_output_schemas else "serialization"
     )
     # This expects that GenerateJsonSchema was already used to generate the definitions
     json_schema = field_mapping[(field, override_mode or field.mode)]
@@ -204,8 +204,8 @@ def get_definitions(
     Dict[str, Dict[str, Any]],
 ]:
     schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)
-    override_mode: Union[Literal["validation"], None] = (
-        None if separate_input_output_schemas else "validation"
+    override_mode: Union[Literal["serialization"], None] = (
+        None if separate_input_output_schemas else "serialization"
     )
     validation_fields = [field for field in fields if field.mode == "validation"]
     serialization_fields = [field for field in fields if field.mode == "serialization"]

Test output

show
......                                                                   [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
6 passed, 1 warning in 0.74s