← eval7_submission_v1n

fastapi_13207

failed CONTEXT UNSUBMITTED agent_error · 37 tool calls · 106 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 (37)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"separate_input_output_schemas\" . | head -20"
}
{"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", "stderr": "grep: ./.git/index: binary file matches\n", "exit_code": 0}
2run_command
{
 "command": "grep -rn \"computed_field\" . | head -20"
}
{"status": "ok", "stdout": "./tests/test_computed_fields.py:12: from pydantic import BaseModel, computed_field\n./tests/test_computed_fields.py:18: @computed_fi
full
{"status": "ok", "stdout": "./tests/test_computed_fields.py:12:    from pydantic import BaseModel, computed_field\n./tests/test_computed_fields.py:18:        @computed_field\n./docs/en/docs/release-notes.md:2807:* \u2728 Enable Pydantic's serialization mode for responses, add support for Pydantic's `computed_field`, better OpenAPI for response models, proper required attributes, better generated clients. PR [#10011](https://github.com/tiangolo/fastapi/pull/10011) by [@tiangolo](https://github.com/tiangolo).\n", "stderr": "grep: ./.git/index: binary file matches\n", "exit_code": 0}
3run_command
{
 "command": "cat tests/test_computed_fields.py"
}
{"status": "ok", "stdout": "import pytest\nfrom fastapi import FastAPI\nfrom fastapi.testclient import TestClient\n\nfrom .utils import needs_pydanticv2\n\n\n@p
full
{"status": "ok", "stdout": "import pytest\nfrom fastapi import FastAPI\nfrom fastapi.testclient import TestClient\n\nfrom .utils import needs_pydanticv2\n\n\n@pytest.fixture(name=\"client\")\ndef get_client():\n    app = FastAPI()\n\n    from pydantic import BaseModel, computed_field\n\n    class Rectangle(BaseModel):\n        width: int\n        length: int\n\n        @computed_field\n        @property\n        def area(self) -> int:\n            return self.width * self.length\n\n    @app.get(\"/\")\n    def read_root() -> Rectangle:\n        return Rectangle(width=3, length=4)\n\n    @app.get(\"/responses\", responses={200: {\"model\": Rectangle}})\n    def read_responses() -> Rectangle:\n        return Rectangle(width=3, length=4)\n\n    client = TestClient(app)\n    return client\n\n\n@pytest.mark.parametrize(\"path\", [\"/\", \"/responses\"])\n@needs_pydanticv2\ndef test_get(client: TestClient, path: str):\n    response = client.get(path)\n    assert response.status_code == 200, response.text\n    assert response.json() == {\"width\": 3, \"length\": 4, \"area\": 12}\n\n\n@needs_pydanticv2\ndef test_openapi_schema(client: TestClient):\n    response = client.get(\"/openapi.json\")\n    assert response.status_code == 200, response.text\n    assert response.json() == {\n        \"openapi\": \"3.1.0\",\n        \"info\": {\"title\": \"FastAPI\", \"version\": \"0.1.0\"},\n        \"paths\": {\n            \"/\": {\n                \"get\": {\n                    \"summary\": \"Read Root\",\n                    \"operationId\": \"read_root__get\",\n                    \"responses\": {\n                        \"200\": {\n                            \"description\": \"Successful Response\",\n                            \"content\": {\n                                \"application/json\": {\n                                    \"schema\": {\"$ref\": \"#/components/schemas/Rectangle\"}\n                                }\n                            },\n                 
4write_file
{
 "content": "import pytest\nfrom fastapi import FastAPI\nfrom fastapi.testclient import TestClient\nfrom pydantic import BaseModel, computed_field\n\ndef test_computed_fields_separate_input_output_schemas_false():\n    # separate_input_output_schemas=False should still include computed fields in the schema\n    app = FastAPI(separate_input_output_schemas=False)\n\n    class Rectangle(BaseModel):\n        width: int\n        length: int\n\n        @computed_field\n        @property\n        def area(self) -> int:\n            return self.width * self.length\n\n    @app.get(\"/\")\n    def read_root() -> Rectangle:\n        return Rectangle(width=3, length=4)\n\n    client = TestClient(app)\n    response = client.get(\"/openapi.json\")\n    assert response.status_code == 200\n    schema = response.json()\n    \n    # Check if Rectangle schema exists\n    rectangle_schema = schema[\"components\"][\"schemas\"][\"Rectangle\"]\n    assert \"area\" in rectangle_schema[\"properties\"], \"Computed field 'area' should be in the schema even when separate_input_output_schemas=False\"\n    assert rectangle_schema[\"properties\"][\"area\"][\"readOnly\"] is True\n",
 "filepath": "tests/test_computed_fields_issue.py"
}
{"status": "ok", "filepath": "tests/test_computed_fields_issue.py", "size": 1101}
full
{"status": "ok", "filepath": "tests/test_computed_fields_issue.py", "size": 1101}
5run_command
{
 "command": "pytest tests/test_computed_fields_issue.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "============================= test session starts ==============================\nplatform l
full
{"status": "error", "error_type": "CommandError", "error_message": "============================= test session starts ==============================\nplatform linux -- Python 3.13.14, pytest-9.1.1, pluggy-1.6.0\ncodspeed: 5.0.3 (disabled, mode: walltime, callgraph: enabled, timer_resolution: 1.0ns)\nrootdir: /workspace\nconfigfile: pytest.ini (WARNING: ignoring pytest config in pyproject.toml!)\nplugins: anyio-4.15.1, sugar-1.1.1, inline-snapshot-0.35.4, timeout-2.4.0, xdist-3.8.0, codspeed-5.0.3, cov-7.1.0\ncollected 1 item\n\ntests/test_computed_fields_issue.py F                                    [100%]\n\n=================================== FAILURES ===================================\n___________ test_computed_fields_separate_input_output_schemas_false ___________\n\n    def test_computed_fields_separate_input_output_schemas_false():\n        # separate_input_output_schemas=False should still include computed fields in the schema\n        app = FastAPI(separate_input_output_schemas=False)\n    \n        class Rectangle(BaseModel):\n            width: int\n            length: int\n    \n            @computed_field\n            @property\n            def area(self) -> int:\n                return self.width * self.length\n    \n        @app.get(\"/\")\n        def read_root() -> Rectangle:\n            return Rectangle(width=3, length=4)\n    \n        client = TestClient(app)\n        response = client.get(\"/openapi.json\")\n        assert response.status_code == 200\n        schema = response.json()\n    \n        # Check if Rectangle schema exists\n        rectangle_schema = schema[\"components\"][\"schemas\"][\"Rectangle\"]\n>       assert \"area\" in rectangle_schema[\"properties\"], \"Computed field 'area' should be in the schema even when separate_input_output_schemas=False\"\nE       AssertionError: Computed field 'area' should be in the schema even when separate_input_output_schemas=False\nE       assert 'area' in {'width': {'type': 'integer', 'title': 
6run_command
{
 "command": "cat fastapi/openapi/utils.py"
}
{"status": "ok", "stdout": "import http.client\nimport inspect\nimport warnings\nfrom typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union
full
{"status": "ok", "stdout": "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            \"type\": \"array\",\n            \"
7run_command
{
 "command": "grep -n \"get_openapi_route\" fastapi/openapi/utils.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
full
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
8run_command
{
 "command": "grep -n \"get_openapi\" fastapi/openapi/utils.py"
}
{"status": "ok", "stdout": "78:def get_openapi_security_definitions(\n95:def _get_openapi_operation_parameters(\n168:def get_openapi_operation_request_body(\n22
full
{"status": "ok", "stdout": "78:def get_openapi_security_definitions(\n95:def _get_openapi_operation_parameters(\n168:def get_openapi_operation_request_body(\n224:def get_openapi_operation_metadata(\n250:def get_openapi_path(\n272:            operation = get_openapi_operation_metadata(\n277:            security_definitions, operation_security = get_openapi_security_definitions(\n284:            operation_parameters = _get_openapi_operation_parameters(\n305:                request_body_oai = get_openapi_operation_request_body(\n321:                        ) = get_openapi_path(\n467:def get_openapi(\n511:            result = get_openapi_path(\n530:            result = get_openapi_path(\n", "stderr": "", "exit_code": 0}
9run_command
{
 "command": "sed -n '250,466p' fastapi/openapi/utils.py"
}
{"status": "ok", "stdout": "def get_openapi_path(\n *,\n route: routing.APIRoute,\n operation_ids: Set[str],\n model_name_map: ModelNameMap,\n field_mapping: Di
full
{"status": "ok", "stdout": "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            parameters.extend(operation_parameters)\
10run_command
{
 "command": "sed -n '410,466p' fastapi/openapi/utils.py"
}
{"status": "ok", "stdout": " all_route_params = get_flat_params(route.dependant)\n if (all_route_params or route.body_field) and not any(\n status in operation[
full
{"status": "ok", "stdout": "            all_route_params = get_flat_params(route.dependant)\n            if (all_route_params or route.body_field) and not any(\n                status in operation[\"responses\"]\n                for status in [http422, \"4XX\", \"default\"]\n            ):\n                operation[\"responses\"][http422] = {\n                    \"description\": \"Validation Error\",\n                    \"content\": {\n                        \"application/json\": {\n                            \"schema\": {\"$ref\": REF_PREFIX + \"HTTPValidationError\"}\n                        }\n                    },\n                }\n                if \"ValidationError\" not in definitions:\n                    definitions.update(\n                        {\n                            \"ValidationError\": validation_error_definition,\n                            \"HTTPValidationError\": validation_error_response_definition,\n                        }\n                    )\n            if route.openapi_extra:\n                deep_dict_update(operation, route.openapi_extra)\n            path[method.lower()] = operation\n    return path, security_schemes, definitions\n\n\ndef get_fields_from_routes(\n    routes: Sequence[BaseRoute],\n) -> List[ModelField]:\n    body_fields_from_routes: List[ModelField] = []\n    responses_from_routes: List[ModelField] = []\n    request_fields_from_routes: List[ModelField] = []\n    callback_flat_models: List[ModelField] = []\n    for route in routes:\n        if getattr(route, \"include_in_schema\", None) and isinstance(\n            route, routing.APIRoute\n        ):\n            if route.body_field:\n                assert _is_model_field(route.body_field), (\n                    \"A request body must be a Pydantic Field\"\n                )\n                body_fields_from_routes.append(route.body_field)\n            if route.response_field:\n                responses_from_routes.append(route.response_field)\n       
11run_command
{
 "command": "grep -n \"get_schema_from_model_field\" fastapi/openapi/utils.py"
}
{"status": "ok", "stdout": "13: get_schema_from_model_field,\n129: param_schema = get_schema_from_model_field(\n180: body_schema = get_schema_from_model_field(\
full
{"status": "ok", "stdout": "13:    get_schema_from_model_field,\n129:            param_schema = get_schema_from_model_field(\n180:    body_schema = get_schema_from_model_field(\n352:                        response_schema = get_schema_from_model_field(\n385:                        additional_field_schema = get_schema_from_model_field(\n", "stderr": "", "exit_code": 0}
12run_command
{
 "command": "cat fastapi/_compat/main.py"
}
{"status": "ok", "stdout": "import sys\nfrom functools import lru_cache\nfrom typing import (\n Any,\n Dict,\n List,\n Sequence,\n Tuple,\n Type,\n)\n\nfrom fas
full
{"status": "ok", "stdout": "import sys\nfrom functools import lru_cache\nfrom typing import (\n    Any,\n    Dict,\n    List,\n    Sequence,\n    Tuple,\n    Type,\n)\n\nfrom fastapi._compat import may_v1\nfrom fastapi._compat.shared import PYDANTIC_V2, lenient_issubclass\nfrom fastapi.types import ModelNameMap\nfrom pydantic import BaseModel\nfrom typing_extensions import Literal\n\nfrom .model_field import ModelField\n\nif PYDANTIC_V2:\n    from .v2 import BaseConfig as BaseConfig\n    from .v2 import FieldInfo as FieldInfo\n    from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError\n    from .v2 import RequiredParam as RequiredParam\n    from .v2 import Undefined as Undefined\n    from .v2 import UndefinedType as UndefinedType\n    from .v2 import Url as Url\n    from .v2 import Validator as Validator\n    from .v2 import evaluate_forwardref as evaluate_forwardref\n    from .v2 import get_missing_field_error as get_missing_field_error\n    from .v2 import (\n        with_info_plain_validator_function as with_info_plain_validator_function,\n    )\nelse:\n    from .v1 import BaseConfig as BaseConfig  # type: ignore[assignment]\n    from .v1 import FieldInfo as FieldInfo\n    from .v1 import (  # type: ignore[assignment]\n        PydanticSchemaGenerationError as PydanticSchemaGenerationError,\n    )\n    from .v1 import RequiredParam as RequiredParam\n    from .v1 import Undefined as Undefined\n    from .v1 import UndefinedType as UndefinedType\n    from .v1 import Url as Url  # type: ignore[assignment]\n    from .v1 import Validator as Validator\n    from .v1 import evaluate_forwardref as evaluate_forwardref\n    from .v1 import get_missing_field_error as get_missing_field_error\n    from .v1 import (  # type: ignore[assignment]\n        with_info_plain_validator_function as with_info_plain_validator_function,\n    )\n\n\n@lru_cache\ndef get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n    if lenient_issubclass(model, ma
13run_command
{
 "command": "cat fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "import re\nimport warnings\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass\nfrom enum import Enum\nfrom typing i
full
{"status": "ok", "stdout": "import re\nimport warnings\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass\nfrom enum import Enum\nfrom typing import (\n    Any,\n    Dict,\n    List,\n    Sequence,\n    Set,\n    Tuple,\n    Type,\n    Union,\n    cast,\n)\n\nfrom fastapi._compat import may_v1, shared\nfrom fastapi.openapi.constants import REF_TEMPLATE\nfrom fastapi.types import IncEx, ModelNameMap\nfrom pydantic import BaseModel, TypeAdapter, create_model\nfrom pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError\nfrom pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation\nfrom pydantic import ValidationError as ValidationError\nfrom pydantic._internal._schema_generation_shared import (  # type: ignore[attr-defined]\n    GetJsonSchemaHandler as GetJsonSchemaHandler,\n)\nfrom pydantic._internal._typing_extra import eval_type_lenient\nfrom pydantic._internal._utils import lenient_issubclass as lenient_issubclass\nfrom pydantic.fields import FieldInfo as FieldInfo\nfrom pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema\nfrom pydantic.json_schema import JsonSchemaValue as JsonSchemaValue\nfrom pydantic_core import CoreSchema as CoreSchema\nfrom pydantic_core import PydanticUndefined, PydanticUndefinedType\nfrom pydantic_core import Url as Url\nfrom typing_extensions import Annotated, Literal, get_args, get_origin\n\ntry:\n    from pydantic_core.core_schema import (\n        with_info_plain_validator_function as with_info_plain_validator_function,\n    )\nexcept ImportError:  # pragma: no cover\n    from pydantic_core.core_schema import (\n        general_plain_validator_function as with_info_plain_validator_function,  # noqa: F401\n    )\n\nRequiredParam = PydanticUndefined\nUndefined = PydanticUndefined\nUndefinedType = PydanticUndefinedType\nevaluate_forwardref = eval_type_lenient\nValidator = Any\n\n\nclass BaseConfig:\n    pass\n\n\nclass ErrorWrapper(Exception):\n    pass\n\n\n@data
14run_command
{
 "command": "sed -n '170,210p' fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "def _get_model_config(model: BaseModel) -> Any:\n return model.model_config\n\n\ndef get_schema_from_model_field(\n *,\n field: Mode
full
{"status": "ok", "stdout": "def _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 separate_input_output_schemas else \"validation\"\n    )\n    validation_fields = [field for field in fields if field.mode == \"validation\"]\n", "stderr": "", "exit_code": 0}
15run_command
{
 "command": "sed -n '211,300p' fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": " serialization_fields = [field for field in fields if field.mode == \"serialization\"]\n flat_validation_models = get_flat_models_fr
full
{"status": "ok", "stdout": "    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        ModelField(\n            field_info=FieldInfo(annotation=model),\n            name=model.__name__,\n            mode=\"serialization\",\n        )\n        for model in flat_serialization_models\n    ]\n    flat_model_fields = flat_validation_model_fields + flat_serialization_model_fields\n    input_types = {f.type_ for f in fields}\n    unique_flat_model_fields = {\n        f for f in flat_model_fields if f.type_ not in input_types\n    }\n\n    inputs = [\n        (field, override_mode or field.mode, field._type_adapter.core_schema)\n        for field in list(fields) + list(unique_flat_model_fields)\n    ]\n    field_mapping, definitions = schema_generator.generate_definitions(inputs=inputs)\n    for item_def in cast(Dict[str, Dict[str, Any]], definitions).values():\n        if \"description\" in item_def:\n            item_description = cast(str, item_def[\"description\"]).split(\"\\f\")[0]\n            item_def[\"description\"] = item_description\n    new_mapping, new_definitions = _remap_definitions_and_field_mappings(\n        model_name_map=model_name_map,\n        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
16run_command
{
 "command": "sed -n '220,230p' fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": " field_info=FieldInfo(annotation=model),\n name=model.__name__,\n mode=\"validation\",\n )\n for model in flat_validation_models\n ]
full
{"status": "ok", "stdout": "            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        ModelField(\n            field_info=FieldInfo(annotation=model),\n            name=model.__name__,\n            mode=\"serialization\",\n", "stderr": "", "exit_code": 0}
17run_command
{
 "command": "grep -n \"get_flat_models_from_fields\" fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "212: flat_validation_models = get_flat_models_from_fields(\n215: flat_serialization_models = get_flat_models_from_fields(\n442: get_
full
{"status": "ok", "stdout": "212:    flat_validation_models = get_flat_models_from_fields(\n215:    flat_serialization_models = get_flat_models_from_fields(\n442:    get_flat_models_from_fields(fields, known_models=known_models)\n477:def get_flat_models_from_fields(\n", "stderr": "", "exit_code": 0}
18run_command
{
 "command": "sed -n '477,550p' fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "def get_flat_models_from_fields(\n fields: Sequence[ModelField], known_models: TypeModelSet\n) -> TypeModelSet:\n for field in field
full
{"status": "ok", "stdout": "def get_flat_models_from_fields(\n    fields: Sequence[ModelField], known_models: TypeModelSet\n) -> TypeModelSet:\n    for field in fields:\n        get_flat_models_from_field(field, known_models=known_models)\n    return known_models\n\n\ndef get_long_model_name(model: TypeModelOrEnum) -> str:\n    return f\"{model.__module__}__{model.__qualname__}\".replace(\".\", \"__\")\n", "stderr": "", "exit_code": 0}
19run_command
{
 "command": "grep -n \"get_flat_models_from_field\" fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "212: flat_validation_models = get_flat_models_from_fields(\n215: flat_serialization_models = get_flat_models_from_fields(\n442: get_
full
{"status": "ok", "stdout": "212:    flat_validation_models = get_flat_models_from_fields(\n215:    flat_serialization_models = get_flat_models_from_fields(\n442:    get_flat_models_from_fields(fields, known_models=known_models)\n461:def get_flat_models_from_field(\n477:def get_flat_models_from_fields(\n481:        get_flat_models_from_field(field, known_models=known_models)\n", "stderr": "", "exit_code": 0}
20run_command
{
 "command": "sed -n '461,500p' fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "def get_flat_models_from_field(\n field: ModelField, known_models: TypeModelSet\n) -> TypeModelSet:\n field_type = field.type_\n if
full
{"status": "ok", "stdout": "def get_flat_models_from_field(\n    field: ModelField, known_models: TypeModelSet\n) -> TypeModelSet:\n    field_type = field.type_\n    if lenient_issubclass(field_type, BaseModel):\n        if field_type in known_models:\n            return known_models\n        known_models.add(field_type)\n        get_flat_models_from_model(field_type, known_models=known_models)\n    elif lenient_issubclass(field_type, Enum):\n        known_models.add(field_type)\n    else:\n        get_flat_models_from_annotation(field_type, known_models=known_models)\n    return known_models\n\n\ndef get_flat_models_from_fields(\n    fields: Sequence[ModelField], known_models: TypeModelSet\n) -> TypeModelSet:\n    for field in fields:\n        get_flat_models_from_field(field, known_models=known_models)\n    return known_models\n\n\ndef get_long_model_name(model: TypeModelOrEnum) -> str:\n    return f\"{model.__module__}__{model.__qualname__}\".replace(\".\", \"__\")\n", "stderr": "", "exit_code": 0}
21run_command
{
 "command": "grep -n \"get_flat_models_from_model\" fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "437:def get_flat_models_from_model(\n455: get_flat_models_from_model(arg, known_models=known_models)\n469: get_flat_models_from_mode
full
{"status": "ok", "stdout": "437:def get_flat_models_from_model(\n455:                    get_flat_models_from_model(arg, known_models=known_models)\n469:        get_flat_models_from_model(field_type, known_models=known_models)\n", "stderr": "", "exit_code": 0}
22run_command
{
 "command": "sed -n '437,460p' fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "def get_flat_models_from_model(\n model: Type[\"BaseModel\"], known_models: Union[TypeModelSet, None] = None\n) -> TypeModelSet:\n k
full
{"status": "ok", "stdout": "def get_flat_models_from_model(\n    model: Type[\"BaseModel\"], known_models: Union[TypeModelSet, None] = None\n) -> TypeModelSet:\n    known_models = known_models or set()\n    fields = get_model_fields(model)\n    get_flat_models_from_fields(fields, known_models=known_models)\n    return known_models\n\n\ndef get_flat_models_from_annotation(\n    annotation: Any, known_models: TypeModelSet\n) -> TypeModelSet:\n    origin = get_origin(annotation)\n    if origin is not None:\n        for arg in get_args(annotation):\n            if lenient_issubclass(arg, (BaseModel, Enum)) and arg not in known_models:\n                known_models.add(arg)\n                if lenient_issubclass(arg, BaseModel):\n                    get_flat_models_from_model(arg, known_models=known_models)\n            else:\n                get_flat_models_from_annotation(arg, known_models=known_models)\n    return known_models\n\n\n", "stderr": "", "exit_code": 0}
23run_command
{
 "command": "grep -n \"get_model_fields\" fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "401:def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n441: fields = get_model_fields(model)\n", "stderr": "", "exit
full
{"status": "ok", "stdout": "401:def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n441:    fields = get_model_fields(model)\n", "stderr": "", "exit_code": 0}
24run_command
{
 "command": "sed -n '401,436p' fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n return [\n ModelField(field_info=field_info, name=name)\n for na
full
{"status": "ok", "stdout": "def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n    return [\n        ModelField(field_info=field_info, name=name)\n        for name, field_info in model.model_fields.items()\n    ]\n\n\n# Duplicate of several schema functions from Pydantic v1 to make them compatible with\n# Pydantic v2 and allow mixing the models\n\nTypeModelOrEnum = Union[Type[\"BaseModel\"], Type[Enum]]\nTypeModelSet = Set[TypeModelOrEnum]\n\n\ndef normalize_name(name: str) -> str:\n    return re.sub(r\"[^a-zA-Z0-9.\\-_]\", \"_\", name)\n\n\ndef get_model_name_map(unique_models: TypeModelSet) -> Dict[TypeModelOrEnum, str]:\n    name_model_map = {}\n    conflicting_names: Set[str] = set()\n    for model in unique_models:\n        model_name = normalize_name(model.__name__)\n        if model_name in conflicting_names:\n            model_name = get_long_model_name(model)\n            name_model_map[model_name] = model\n        elif model_name in name_model_map:\n            conflicting_names.add(model_name)\n            conflicting_model = name_model_map.pop(model_name)\n            name_model_map[get_long_model_name(conflicting_model)] = conflicting_model\n            name_model_map[get_long_model_name(model)] = model\n        else:\n            name_model_map[model_name] = model\n    return {v: k for k, v in name_model_map.items()}\n\n\n", "stderr": "", "exit_code": 0}
25run_command
{
 "command": "grep -n \"model_fields\" fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "218: flat_validation_model_fields = [\n226: flat_serialization_model_fields = [\n234: flat_model_fields = flat_validation_model_fiel
full
{"status": "ok", "stdout": "218:    flat_validation_model_fields = [\n226:    flat_serialization_model_fields = [\n234:    flat_model_fields = flat_validation_model_fields + flat_serialization_model_fields\n236:    unique_flat_model_fields = {\n237:        f for f in flat_model_fields if f.type_ not in input_types\n242:        for field in list(fields) + list(unique_flat_model_fields)\n401:def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n404:        for name, field_info in model.model_fields.items()\n441:    fields = get_model_fields(model)\n", "stderr": "", "exit_code": 0}
26run_command
{
 "command": "cat fastapi/_compat/v2.py | grep -n \"get_model_fields\""
}
{"status": "ok", "stdout": "401:def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n441: fields = get_model_fields(model)\n", "stderr": "", "exit
full
{"status": "ok", "stdout": "401:def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n441:    fields = get_model_fields(model)\n", "stderr": "", "exit_code": 0}
27run_command
{
 "command": "sed -n '401,406p' fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n return [\n ModelField(field_info=field_info, name=name)\n for na
full
{"status": "ok", "stdout": "def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n    return [\n        ModelField(field_info=field_info, name=name)\n        for name, field_info in model.model_fields.items()\n    ]\n\n", "stderr": "", "exit_code": 0}
28run_command
{
 "command": "grep -n \"computed_field\" fastapi/_compat/v2.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
full
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
29run_command
{
 "command": "grep -n \"computed\" fastapi/_compat/v2.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
full
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
30run_command
{
 "command": "cat fastapi/_compat/v2.py | grep \"model_fields\""
}
{"status": "ok", "stdout": " flat_validation_model_fields = [\n flat_serialization_model_fields = [\n flat_model_fields = flat_validation_model_fields + flat_se
full
{"status": "ok", "stdout": "    flat_validation_model_fields = [\n    flat_serialization_model_fields = [\n    flat_model_fields = flat_validation_model_fields + flat_serialization_model_fields\n    unique_flat_model_fields = {\n        f for f in flat_model_fields if f.type_ not in input_types\n        for field in list(fields) + list(unique_flat_model_fields)\ndef get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n        for name, field_info in model.model_fields.items()\n    fields = get_model_fields(model)\n", "stderr": "", "exit_code": 0}
31run_command
{
 "command": "python3 -c \"from pydantic import BaseModel, computed_field; class R(BaseModel): width: int; @computed_field @property def area(self): return self.width; print(R.model_fields)\""
}
{"status": "error", "error_type": "CommandError", "error_message": " File \"<string>\", line 1\n from pydantic import BaseModel, computed_field; class R(BaseMod
full
{"status": "error", "error_type": "CommandError", "error_message": "  File \"<string>\", line 1\n    from pydantic import BaseModel, computed_field; class R(BaseModel): width: int; @computed_field @property def area(self): return self.width; print(R.model_fields)\n                                                    ^^^^^\nSyntaxError: invalid syntax\n", "details": {"stdout": "", "stderr": "  File \"<string>\", line 1\n    from pydantic import BaseModel, computed_field; class R(BaseModel): width: int; @computed_field @property def area(self): return self.width; print(R.model_fields)\n                                                    ^^^^^\nSyntaxError: invalid syntax\n", "exit_code": 1}}
32run_command
{
 "command": "python3 -c \"from pydantic import BaseModel, computed_field; \nclass R(BaseModel): \n    width: int \n    @computed_field \n    @property \n    def area(self): return self.width \nprint(R.model_fields)\""
}
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<string>\", line 2, in <module>\n class R(BaseMo
full
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n  File \"<string>\", line 2, in <module>\n    class R(BaseModel):\n    ...<3 lines>...\n        def area(self): return self.width\n  File \"/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/pydantic/_internal/_model_construction.py\", line 256, in __new__\n    complete_model_class(\n    ~~~~~~~~~~~~~~~~~~~~^\n        cls,\n        ^^^^\n    ...<3 lines>...\n        create_model_module=_create_model_module,\n        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    )\n    ^\n  File \"/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/pydantic/_internal/_model_construction.py\", line 667, in complete_model_class\n    schema = gen_schema.generate_schema(cls)\n  File \"/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py\", line 744, in generate_schema\n    schema = self._generate_schema_inner(obj)\n  File \"/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py\", line 1038, in _generate_schema_inner\n    return self._model_schema(obj)\n           ~~~~~~~~~~~~~~~~~~^^^^^\n  File \"/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py\", line 870, in _model_schema\n    self._computed_field_schema(d, decorators.field_serializers)\n    ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py\", line 2161, in _computed_field_schema\n    raise PydanticUserError(\n    ...<3 lines>...\n    )\npydantic.errors.PydanticUserError: Computed field is missing return type annotation or specifying `return_type` to the `@computed_field` decorator (e.g. `@computed_field(return_type=int | str)`)\n\nFor further information visit https://errors.pydantic.dev/2.13/u/model-field-missing-annotation\n", "details": {"stdout": "", "stderr": "Traceback (most recent call l
33run_command
{
 "command": "python3 -c \"from pydantic import BaseModel, computed_field; \nclass R(BaseModel): \n    width: int \n    @computed_field \n    @property \n    def area(self) -> int: return self.width \nprint(R.model_fields)\""
}
{"status": "ok", "stdout": "{'width': FieldInfo(annotation=int, required=True)}\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "{'width': FieldInfo(annotation=int, required=True)}\n", "stderr": "", "exit_code": 0}
34run_command
{
 "command": "python3 -c \"from pydantic import BaseModel, computed_field; \nclass R(BaseModel): \n    width: int \n    @computed_field \n    @property \n    def area(self) -> int: return self.width \nprint(R.model_computed_fields)\""
}
{"status": "ok", "stdout": "{'area': ComputedFieldInfo(wrapped_property=<property object at 0x7ee83cfc3740>, return_type=<class 'int'>, alias=None, alias_priori
full
{"status": "ok", "stdout": "{'area': ComputedFieldInfo(wrapped_property=<property object at 0x7ee83cfc3740>, return_type=<class 'int'>, alias=None, alias_priority=None, exclude_if=None, title=None, field_title_generator=None, description=None, deprecated=None, examples=None, json_schema_extra=None, repr=True)}\n", "stderr": "", "exit_code": 0}
35run_command
{
 "command": "sed -n '401,406p' fastapi/_compat/v2.py"
}
{"status": "ok", "stdout": "def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n return [\n ModelField(field_info=field_info, name=name)\n for na
full
{"status": "ok", "stdout": "def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n    return [\n        ModelField(field_info=field_info, name=name)\n        for name, field_info in model.model_fields.items()\n    ]\n\n", "stderr": "", "exit_code": 0}
36edit_file
{
 "filepath": "fastapi/_compat/v2.py",
 "new_string": "def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n    fields = [\n        ModelField(field_info=field_info, name=name)\n        for name, field_info in model.model_fields.items()\n    ]\n    if hasattr(model, \"model_computed_fields\"):\n        for name, field_info in model.model_computed_fields.items():\n            fields.append(ModelField(field_info=field_info, name=name))\n    return fields\n",
 "old_string": "def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n    return [\n        ModelField(field_info=field_info, name=name)\n        for name, field_info in model.model_fields.items()\n    ]\n"
}
{"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@@ -399,10 +399,14 @@\n \n \n def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:\n-    return [\n+    fields = [\n         ModelField(field_info=field_info, name=name)\n         for name, field_info in model.model_fields.items()\n     ]\n+    if hasattr(model, \"model_computed_fields\"):\n+        for name, field_info in model.model_computed_fields.items():\n+            fields.append(ModelField(field_info=field_info, name=name))\n+    return fields\n \n \n # Duplicate of several schema functions from Pydantic v1 to make them compatible with\n", "is_truncated": false}
37run_command
{
 "command": "pytest tests/test_computed_fields_issue.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "============================= test session starts ==============================\nplatform l
full
{"status": "error", "error_type": "CommandError", "error_message": "============================= test session starts ==============================\nplatform linux -- Python 3.13.14, pytest-9.1.1, pluggy-1.6.0\ncodspeed: 5.0.3 (disabled, mode: walltime, callgraph: enabled, timer_resolution: 1.0ns)\nrootdir: /workspace\nconfigfile: pytest.ini (WARNING: ignoring pytest config in pyproject.toml!)\nplugins: anyio-4.15.1, sugar-1.1.1, inline-snapshot-0.35.4, timeout-2.4.0, xdist-3.8.0, codspeed-5.0.3, cov-7.1.0\ncollected 1 item\n\ntests/test_computed_fields_issue.py F                                    [100%]\n\n=================================== FAILURES ===================================\n___________ test_computed_fields_separate_input_output_schemas_false ___________\n\n    def test_computed_fields_separate_input_output_schemas_false():\n        # separate_input_output_schemas=False should still include computed fields in the schema\n        app = FastAPI(separate_input_output_schemas=False)\n    \n        class Rectangle(BaseModel):\n            width: int\n            length: int\n    \n            @computed_field\n            @property\n            def area(self) -> int:\n                return self.width * self.length\n    \n        @app.get(\"/\")\n        def read_root() -> Rectangle:\n            return Rectangle(width=3, length=4)\n    \n        client = TestClient(app)\n>       response = client.get(\"/openapi.json\")\n                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\ntests/test_computed_fields_issue.py:24: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:473: in get\n    return super().get(\n/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/httpx/_client.py:1053: in get\n    return self.request(\n/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:445: in request\n    return super().request(\n/kaggle/tmp/envs/

Patch

diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py
index 3d91814c..c5c13f3c 100644
--- a/fastapi/_compat/v2.py
+++ b/fastapi/_compat/v2.py
@@ -399,10 +399,14 @@ def create_body_model(
 
 
 def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:
-    return [
+    fields = [
         ModelField(field_info=field_info, name=name)
         for name, field_info in model.model_fields.items()
     ]
+    if hasattr(model, "model_computed_fields"):
+        for name, field_info in model.model_computed_fields.items():
+            fields.append(ModelField(field_info=field_info, name=name))
+    return fields
 
 
 # Duplicate of several schema functions from Pydantic v1 to make them compatible with
diff --git a/tests/test_computed_fields_issue.py b/tests/test_computed_fields_issue.py
new file mode 100644
index 00000000..9fce6df7
--- /dev/null
+++ b/tests/test_computed_fields_issue.py
@@ -0,0 +1,31 @@
+import pytest
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel, computed_field
+
+def test_computed_fields_separate_input_output_schemas_false():
+    # separate_input_output_schemas=False should still include computed fields in the schema
+    app = FastAPI(separate_input_output_schemas=False)
+
+    class Rectangle(BaseModel):
+        width: int
+        length: int
+
+        @computed_field
+        @property
+        def area(self) -> int:
+            return self.width * self.length
+
+    @app.get("/")
+    def read_root() -> Rectangle:
+        return Rectangle(width=3, length=4)
+
+    client = TestClient(app)
+    response = client.get("/openapi.json")
+    assert response.status_code == 200
+    schema = response.json()
+    
+    # Check if Rectangle schema exists
+    rectangle_schema = schema["components"]["schemas"]["Rectangle"]
+    assert "area" in rectangle_schema["properties"], "Computed field 'area' should be in the schema even when separate_input_output_schemas=False"
+    assert rectangle_schema["properties"]["area"]["readOnly"] is True

Test output

show
 in handle_request
    raise exc
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:345: in handle_request
    portal.call(self.app, scope, receive, send)
/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/anyio/from_thread.py:340: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/root/.local/share/uv/python/cpython-3.13.14-linux-x86_64-gnu/lib/python3.13/concurrent/futures/_base.py:460: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/root/.local/share/uv/python/cpython-3.13.14-linux-x86_64-gnu/lib/python3.13/concurrent/futures/_base.py:402: in __get_result
    raise self._exception
/kaggle/tmp/envs/fastapi/lib/python3.13/site-packages/anyio/from_thread.py:265: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi/applications.py:1139: in __call__
    await super().__call__(scope, receive, send)
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/applications.py:107: in __call__
    await self.middleware_stack(scope, receive, send)
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/middleware/errors.py:186: in __call__
    raise exc
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/routing.py:716: in __call__
    await self.middleware_stack(scope, receive, send)
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/routing.py:736: in app
    await route.handle(scope, receive, send)
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/routing.py:290: in handle
    await self.app(scope, receive, send)
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/routing.py:78: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/routing.py:75: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
fastapi/applications.py:1094: in openapi
    return JSONResponse(self.openapi())
                        ^^^^^^^^^^^^^^
fastapi/applications.py:1065: in openapi
    self.openapi_schema = get_openapi(
fastapi/openapi/utils.py:503: in get_openapi
    model_name_map = get_compat_model_name_map(all_fields)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
fastapi/_compat/main.py:254: in get_compat_model_name_map
    v2_flat_models = v2.get_flat_models_from_fields(
fastapi/_compat/v2.py:485: in get_flat_models_from_fields
    get_flat_models_from_field(field, known_models=known_models)
fastapi/_compat/v2.py:473: in get_flat_models_from_field
    get_flat_models_from_model(field_type, known_models=known_models)
fastapi/_compat/v2.py:445: in get_flat_models_from_model
    fields = get_model_fields(model)
             ^^^^^^^^^^^^^^^^^^^^^^^
fastapi/_compat/v2.py:408: in get_model_fields
    fields.append(ModelField(field_info=field_info, name=name))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<string>:6: in __init__
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = ModelField(field_info=ComputedFieldInfo(wrapped_property=<property object at 0x7b7d47acb4c0>, return_type=<class 'int'..., description=None, deprecated=None, examples=None, json_schema_extra=None, repr=True), name='area', mode='validation')

    def __post_init__(self) -> None:
        with warnings.catch_warnings():
            # Pydantic >= 2.12.0 warns about field specific metadata that is unused
            # (e.g. `TypeAdapter(Annotated[int, Field(alias='b')])`). In some cases, we
            # end up building the type adapter from a model field annotation so we
            # need to ignore the warning:
            if shared.PYDANTIC_VERSION_MINOR_TUPLE >= (2, 12):
                from pydantic.warnings import UnsupportedFieldAttributeWarning
    
                warnings.simplefilter(
                    "ignore", category=UnsupportedFieldAttributeWarning
                )
            self._type_adapter: TypeAdapter[Any] = TypeAdapter(
>               Annotated[self.field_info.annotation, self.field_info]
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^
            )
E           AttributeError: 'ComputedFieldInfo' object has no attribute 'annotation'

fastapi/_compat/v2.py:98: AttributeError
=============================== 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
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 4 passed, 1 warning in 0.94s