resolved RESOLVED PASS · 28 tool calls · 172 s · fastapi/fastapi
🐛 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 | Arguments | Result |
|---|---|---|---|
| 1 | run_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_schefull{"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 |
| 2 | read_file | {
"end_line": 540,
"filepath": "fastapi/openapi/utils.py",
"start_line": 100
} | {"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n ],\n sepafull{"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 |
| 3 | read_file | {
"end_line": 535,
"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_nafull{"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 |
| 4 | run_command | {
"command": "grep -rn \"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", "exit_code": 0} |
| 5 | read_file | {
"end_line": 480,
"filepath": "fastapi/openapi/utils.py",
"start_line": 400
} | {"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " ) or http.client.responses.get(int(additional_status_code))\n description = (\n process_refull{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": " ) or http.client.responses.get(int(additional_status_code))\n description = (\n process_response.get(\"description\")\n or openapi_response.get(\"description\")\n or status_text\n or \"Additional Response\"\n )\n deep_dict_update(openapi_response, process_response)\n openapi_response[\"description\"] = description\n http422 = \"422\"\n 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: |
| 6 | read_file | {
"end_line": 480,
"filepath": "fastapi/openapi/utils.py",
"start_line": 481
} | {"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "", "start_line": 481, "end_line": 480, "total_lines": 558, "is_truncated": false}full{"status": "ok", "filepath": "fastapi/openapi/utils.py", "content": "", "start_line": 481, "end_line": 480, "total_lines": 558, "is_truncated": false} |
| 7 | run_command | {
"command": "grep -n \"def get_schema_from_model_field\" fastapi/openapi/utils.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "", "exit_code": 1}}full{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "", "exit_code": 1}} |
| 8 | run_command | {
"command": "grep -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", "exit_code": 0} |
| 9 | run_command | {
"command": "grep -rn \"def get_schema_from_model_field\" ."
} | {"status": "ok", "stdout": "./fastapi/_compat/v1.py:236:def get_schema_from_model_field(\n./fastapi/_compat/main.py:314:def get_schema_from_model_field(\n./fastfull{"status": "ok", "stdout": "./fastapi/_compat/v1.py:236:def get_schema_from_model_field(\n./fastapi/_compat/main.py:314:def get_schema_from_model_field(\n./fastapi/_compat/v2.py:174:def get_schema_from_model_field(\n", "exit_code": 0} |
| 10 | read_file | {
"end_line": 210,
"filepath": "fastapi/_compat/v2.py",
"start_line": 170
} | {"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "def _get_model_config(model: BaseModel) -> Any:\n return model.model_config\n\n\ndef get_schemfull{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "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", "start_line": 170, "end_line": 210, "total_lines": 486, "is_truncated": false} |
| 11 | read_file | {
"filepath": "fastapi/_compat/v2.py",
"start_line": 150,
"end_line": 220
} | {"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(\nfull{"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 |
| 12 | read_file | {
"end_line": 280,
"filepath": "fastapi/_compat/v2.py",
"start_line": 221
} | {"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": " name=model.__name__,\n mode=\"validation\",\n )\n for model in flat_validation_models\n ]\n ffull{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": " 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 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_s |
| 13 | write_file | {
"content": "from fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\nfrom fastapi.testclient import TestClient\nimport json\n\napp = FastAPI(separate_input_output_schemas=False)\n\nclass Item(BaseModel):\n name: str\n \n @computed_field\n @property\n def description(self) -> str:\n return f\"Description of {self.name}\"\n\n@app.get(\"/item\", response_model=Item)\ndef get_item():\n return Item(name=\"Test Item\")\n\nclient = TestClient(app)\n\ndef test_computed_field_in_openapi():\n openapi_schema = app.openapi()\n # Find the Item schema in components/schemas\n schemas = openapi_schema[\"components\"][\"schemas\"]\n # The schema name might be 'Item' or 'Item_serialization' depending on separate_input_output_schemas\n # But with separate_input_output_schemas=False, it should be 'Item'\n item_schema = schemas.get(\"Item\")\n if not item_schema:\n print(f\"Item schema not found. Available schemas: {list(schemas.keys())}\")\n assert False\n \n properties = item_schema.get(\"properties\", {})\n print(f\"Properties: {list(properties.keys())}\")\n assert \"description\" in properties, \"Computed field 'description' should be in OpenAPI schema even when separate_input_output_schemas=False\"\n\nif __name__ == \"__main__\":\n try:\n test_computed_field_in_openapi()\n print(\"Test passed!\")\n except AssertionError as e:\n print(f\"Test failed: {e}\")\n exit(1)\n",
"filepath": "/tmp/test_computed_field.py"
} | {"status": "ok", "filepath": "/tmp/test_computed_field.py", "size": 1417}full{"status": "ok", "filepath": "/tmp/test_computed_field.py", "size": 1417} |
| 14-15 ×2 | run_command | {
"command": "python3 /tmp/test_computed_field.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "python3: can't open ffull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "python3: can't open file '/tmp/swe_work/eval_submission_analyzer/fastapi_13207/a/tmp/test_computed_field.py': [Errno 2] No such file or directory\n", "exit_code": 2}} |
| 16 | run_command | {
"command": "ls -R /tmp"
} | {"status": "ok", "stdout": "/tmp/swe_work/eval_submission_analyzer/fastapi_13207/a/tmp:\n", "exit_code": 0}full{"status": "ok", "stdout": "/tmp/swe_work/eval_submission_analyzer/fastapi_13207/a/tmp:\n", "exit_code": 0} |
| 17 | run_command | {
"command": "python3 -c \"from fastapi import FastAPI; from pydantic import BaseModel, computed_field; app = FastAPI(separate_input_output_schemas=False); class Item(BaseModel): name: str; @computed_field @property def description(self) -> str: return 'desc'; @app.get('/item', response_model=Item) def get_item(): return Item(name='t'); schema = app.openapi(); print(schema['components']['schemas']['Item']['properties'].keys())\""
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": " File \"<string>\", lfull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": " File \"<string>\", line 1\n from fastapi import FastAPI; from pydantic import BaseModel, computed_field; app = FastAPI(separate_input_output_schemas=False); class Item(BaseModel): name: str; @computed_field @property def description(self) -> str: return 'desc'; @app.get('/item', response_model=Item) def get_item(): return Item(name='t'); schema = app.openapi(); print(schema['components']['schemas']['Item']['properties'].keys())\n ^^^^^\nSyntaxError: invalid syntax\n", "exit_code": 1}} |
| 18 | run_command | {
"command": "python3 -c \"\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\napp = FastAPI(separate_input_output_schemas=False)\nclass Item(BaseModel):\n name: str\n @computed_field\n @property\n def description(self) -> str:\n return 'desc'\n@app.get('/item', response_model=Item)\ndef get_item():\n return Item(name='t')\nschema = app.openapi()\nprint(list(schema['components']['schemas']['Item']['properties'].keys()))\n\""
} | {"status": "ok", "stdout": "['name']\n", "exit_code": 0}full{"status": "ok", "stdout": "['name']\n", "exit_code": 0} |
| 19 | edit_file | {
"filepath": "fastapi/_compat/v2.py",
"new_string": " override_mode: Union[Literal[\\\"validation\\\"], None] = (\n None if separate_input_output_schemas else \\\"validation\\\"\n )\n # If override_mode is \\\"validation\\\", we still want to include computed fields\n # which are only present in \\\"serialization\\\" mode.\n # However, the current implementation of GenerateJsonSchema and field_mapping\n # uses the mode to decide which schema to use.\n # To fix this, we can check if the field is a computed field.\n # But ModelField doesn't directly tell us if it's computed in a simple way.\n # Actually, the issue is that when separate_input_output_schemas=False,\n # we force \\\"validation\\\" mode for everything.\n # For computed fields, field.mode is \\\"serialization\\\".\n # If we use (override_mode or field.mode), and override_mode is \\\"validation\\\",\n # it will always be \\\"validation\\\".\n # We should only override if the field is NOT a computed field.\n # But we don't know if it's computed here easily.\n # Wait, if we just change how override_mode is used:\n # json_schema = field_mapping[(field, override_mode if override_mode and field.mode == \\\"validation\\\" else field.mode)]\n # No, that's not right.\n # If separate_input_output_schemas=False, we want to use \\\"validation\\\" for normal fields\n # and \\\"serialization\\\" for computed fields.\n # But computed fields ONLY have \\\"serialization\\\" mode.\n # So if we use field.mode, it will be \\\"serialization\\\" for computed fields.\n # If we use override_mode (\\\"validation\\\"), it will fail because computed fields\n # are not in the validation mapping.\n # Let's see:\n # json_schema = field_mapping[(field, override_mode or field.mode)]\n # If separate_input_output_schemas=False, override_mode=\\\"validation\\\".\n # For a computed field, field.mode=\\\"serialization\\\".\n # (override_mode or field.mode) -> \\\"validation\\\".\n # But field_mapping only has (field, \\\"serialization\\\") for computed fields.\n # So it should be:\n # mode = override_mode if (override_mode and field.mode == \\\"validation\\\") else field.mode\n # Actually, if separate_input_output_schemas=False, we want to use \\\"validation\\\"\n # for fields that support it, and \\\"serialization\\\" for those that don't (like computed fields).\n # Since computed fields only have \\\"serialization\\\" mode, field.mode will be \\\"serialization\\\".\n # If we use:\n # mode = override_mode if (override_mode and field.mode == \\\"validation\\\") else field.mode\n # For normal field: field.mode=\\\"validation\\\", override_mode=\\\"validation\\\" -> \\\"validation\\\".\n # For computed field: field.mode=\\\"serialization\\\", override_mode=\\\"validation\\\" -> \\\"serialization\\\".\n # This seems correct.\n # Let's check if this breaks anything.\n # If separate_input_output_schemas=True, override_mode=None.\n # mode = None if (None and ...) else field.mode -> field.mode. Correct.\n # If separate_input_output_schemas=False, override_mode=\\\"validation\\\".\n # For normal field (mode=\\\"validation\\\"): mode = \\\"validation\\\" if (\\\"validation\\\" and \\\"validation\\\" == \\\"validation\\\") else \\\"validation\\\" -> \\\"validation\\\".\n # For computed field (mode=\\\"serialization\\\"): mode = \\\"validation\\\" if (\\\"validation\\\" and \\\"serialization\\\" == \\\"validation\\\") else \\\"serialization\\\" -> \\\"serialization\\\".\n # This looks right.\n # Wait, what if a field has both? Pydantic v2 fields usually have one mode in this context.\n # Let's try this.\n json_schema = field_mapping[(field, override_mode if (override_mode and field.mode == \\\"validation\\\") else field.mode)]",
"old_string": " # This expects that GenerateJsonSchema was already used to generate the definitions\\n json_schema = field_ | {"status": "error", "error_type": "FileEditError", "error_message": "old_string not found in file (tried exact, flexible and regex matching)"}full{"status": "error", "error_type": "FileEditError", "error_message": "old_string not found in file (tried exact, flexible and regex matching)"} |
| 20 | read_file | {
"end_line": 195,
"filepath": "fastapi/_compat/v2.py",
"start_line": 170
} | {"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "def _get_model_config(model: BaseModel) -> Any:\n return model.model_config\n\n\ndef get_schemfull{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "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", "start_line": 170, "end_line": 195, "total_lines": 486, "is_truncated": false} |
| 21 | edit_file | {
"filepath": "fastapi/_compat/v2.py",
"new_string": " # This expects that GenerateJsonSchema was already used to generate the definitions\n mode = override_mode if (override_mode and field.mode == \"validation\") else field.mode\n json_schema = field_mapping[(field, mode)]",
"old_string": " # This expects that GenerateJsonSchema was already used to generate the definitions\n json_schema = field_mapping[(field, override_mode or field.mode)]"
} | {"status": "ok", "filepath": "fastapi/_compat/v2.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/_compat/v2.py\n+++ b/fastapi/_compat/v2.py\nfull{"status": "ok", "filepath": "fastapi/_compat/v2.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/_compat/v2.py\n+++ b/fastapi/_compat/v2.py\n@@ -184,7 +184,8 @@\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+ mode = override_mode if (override_mode and field.mode == \"validation\") else field.mode\n+ json_schema = field_mapping[(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", "is_truncated": false} |
| 22 | run_command | {
"command": "python3 -c \"\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\napp = FastAPI(separate_input_output_schemas=False)\nclass Item(BaseModel):\n name: str\n @computed_field\n @property\n def description(self) -> str:\n return 'desc'\n@app.get('/item', response_model=Item)\ndef get_item():\n return Item(name='t')\nschema = app.openapi()\nprint(list(schema['components']['schemas']['Item']['properties'].keys()))\n\""
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recenfull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"<string>\", line 14, in <module>\n schema = app.openapi()\n File \"/workspace/fastapi/applications.py\", line 1065, in openapi\n self.openapi_schema = get_openapi(\n ~~~~~~~~~~~^\n title=self.title,\n ^^^^^^^^^^^^^^^^^\n ...<12 lines>...\n external_docs=self.openapi_external_docs,\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n )\n ^\n File \"/workspace/fastapi/openapi/utils.py\", line 511, in get_openapi\n result = get_openapi_path(\n route=route,\n ...<3 lines>...\n separate_input_output_schemas=separate_input_output_schemas,\n )\n File \"/workspace/fastapi/openapi/utils.py\", line 352, in get_openapi_path\n response_schema = get_schema_from_model_field(\n field=route.response_field,\n ...<2 lines>...\n separate_input_output_schemas=separate_input_output_schemas,\n )\n File \"/workspace/fastapi/_compat/main.py\", line 337, in get_schema_from_model_field\n return v2.get_schema_from_model_field(\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^\n field=field, # type: ignore[arg-type]\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n ...<2 lines>...\n separate_input_output_schemas=separate_input_output_schemas,\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n )\n ^\n File \"/workspace/fastapi/_compat/v2.py\", line 188, in get_schema_from_model_field\n json_schema = field_mapping[(field, mode)]\n ~~~~~~~~~~~~~^^^^^^^^^^^^^^^\nKeyError: (ModelField(field_info=FieldInfo(annotation=Item, required=True), name='Response_get_item_item_get', mode='serialization'), 'serialization')\n", "exit_code": 1}} |
| 23 | read_file | {
"end_line": 270,
"filepath": "fastapi/_compat/v2.py",
"start_line": 240
} | {"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "\n inputs = [\n (field, override_mode or field.mode, field._type_adapter.core_schema)\n for fifull{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "\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 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", "start_line": 240, "end_line": 270, "total_lines": 487, "is_truncated": false} |
| 24 | edit_file | {
"filepath": "fastapi/_compat/v2.py",
"new_string": " inputs = [\n (field, override_mode if (override_mode and field.mode == \"validation\") else field.mode, field._type_adapter.core_schema)\n for field in list(fields) + list(unique_flat_model_fields)\n ]",
"old_string": " 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 ]"
} | {"status": "ok", "filepath": "fastapi/_compat/v2.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/_compat/v2.py\n+++ b/fastapi/_compat/v2.py\nfull{"status": "ok", "filepath": "fastapi/_compat/v2.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/_compat/v2.py\n+++ b/fastapi/_compat/v2.py\n@@ -239,7 +239,7 @@\n }\n \n inputs = [\n- (field, override_mode or field.mode, field._type_adapter.core_schema)\n+ (field, override_mode if (override_mode and field.mode == \"validation\") else 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", "is_truncated": false} |
| 25 | run_command | {
"command": "python3 -c \"\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\napp = FastAPI(separate_input_output_schemas=False)\nclass Item(BaseModel):\n name: str\n @computed_field\n @property\n def description(self) -> str:\n return 'desc'\n@app.get('/item', response_model=Item)\ndef get_item():\n return Item(name='t')\nschema = app.openapi()\nprint(list(schema['components']['schemas']['Item']['properties'].keys()))\n\""
} | {"status": "ok", "stdout": "['name', 'description']\n", "exit_code": 0}full{"status": "ok", "stdout": "['name', 'description']\n", "exit_code": 0} |
| 26 | run_command | {
"command": "python3 -c \"\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\napp = FastAPI(separate_input_output_schemas=True)\nclass Item(BaseModel):\n name: str\n @computed_field\n @property\n def description(self) -> str:\n return 'desc'\n@app.get('/item', response_model=Item)\ndef get_item():\n return Item(name='t')\nschema = app.openapi()\n# When separate_input_output_schemas=True, we expect Item and Item_serialization (or similar)\n# Let's check if computed field is in the serialization schema\nschemas = schema['components']['schemas']\nserialization_schema_name = [k for k in schemas if 'serialization' in k][0]\nprint(f'Serialization schema: {serialization_schema_name}')\nprint(list(schemas[serialization_schema_name]['properties'].keys()))\n\""
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recenfull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"<string>\", line 18, in <module>\n serialization_schema_name = [k for k in schemas if 'serialization' in k][0]\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^\nIndexError: list index out of range\n", "exit_code": 1}} |
| 27 | run_command | {
"command": "python3 -c \"\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\napp = FastAPI(separate_input_output_schemas=True)\nclass Item(BaseModel):\n name: str\n @computed_field\n @property\n def description(self) -> str:\n return 'desc'\n@app.get('/item', response_model=Item)\ndef get_item():\n return Item(name='t')\nschema = app.openapi()\nprint(list(schema['components']['schemas'].keys()))\n\""
} | {"status": "ok", "stdout": "['Item']\n", "exit_code": 0}full{"status": "ok", "stdout": "['Item']\n", "exit_code": 0} |
| 28 | run_command | {
"command": "python3 -c \"\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\napp = FastAPI(separate_input_output_schemas=True)\nclass Item(BaseModel):\n name: str\n @computed_field\n @property\n def description(self) -> str:\n return 'desc'\n@app.get('/item', response_model=Item)\ndef get_item():\n return Item(name='t')\nschema = app.openapi()\nprint(list(schema['components']['schemas']['Item']['properties'].keys()))\n\""
} | {"status": "ok", "stdout": "['name', 'description']\n", "exit_code": 0}full{"status": "ok", "stdout": "['name', 'description']\n", "exit_code": 0} |
| 29 | submit_patch | [] | {"status": "ok", "patch_size": 2859, "files_changed": 2}full{"status": "ok", "patch_size": 2859, "files_changed": 2} |
diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py
index 3d91814c..081c6bac 100644
--- a/fastapi/_compat/v2.py
+++ b/fastapi/_compat/v2.py
@@ -184,7 +184,8 @@ def get_schema_from_model_field(
None if separate_input_output_schemas else "validation"
)
# This expects that GenerateJsonSchema was already used to generate the definitions
- json_schema = field_mapping[(field, override_mode or field.mode)]
+ mode = override_mode if (override_mode and field.mode == "validation") else field.mode
+ json_schema = field_mapping[(field, mode)]
if "$ref" not in json_schema:
# TODO remove when deprecating Pydantic v1
# Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207
@@ -238,7 +239,7 @@ def get_definitions(
}
inputs = [
- (field, override_mode or field.mode, field._type_adapter.core_schema)
+ (field, override_mode if (override_mode and field.mode == "validation") else field.mode, field._type_adapter.core_schema)
for field in list(fields) + list(unique_flat_model_fields)
]
field_mapping, definitions = schema_generator.generate_definitions(inputs=inputs)
diff --git a/tmp/test_computed_field.py b/tmp/test_computed_field.py
new file mode 100644
index 00000000..bb6ba11f
--- /dev/null
+++ b/tmp/test_computed_field.py
@@ -0,0 +1,43 @@
+from fastapi import FastAPI
+from pydantic import BaseModel, computed_field
+from fastapi.testclient import TestClient
+import json
+
+app = FastAPI(separate_input_output_schemas=False)
+
+class Item(BaseModel):
+ name: str
+
+ @computed_field
+ @property
+ def description(self) -> str:
+ return f"Description of {self.name}"
+
+@app.get("/item", response_model=Item)
+def get_item():
+ return Item(name="Test Item")
+
+client = TestClient(app)
+
+def test_computed_field_in_openapi():
+ openapi_schema = app.openapi()
+ # Find the Item schema in components/schemas
+ schemas = openapi_schema["components"]["schemas"]
+ # The schema name might be 'Item' or 'Item_serialization' depending on separate_input_output_schemas
+ # But with separate_input_output_schemas=False, it should be 'Item'
+ item_schema = schemas.get("Item")
+ if not item_schema:
+ print(f"Item schema not found. Available schemas: {list(schemas.keys())}")
+ assert False
+
+ properties = item_schema.get("properties", {})
+ print(f"Properties: {list(properties.keys())}")
+ assert "description" in properties, "Computed field 'description' should be in OpenAPI schema even when separate_input_output_schemas=False"
+
+if __name__ == "__main__":
+ try:
+ test_computed_field_in_openapi()
+ print("Test passed!")
+ except AssertionError as e:
+ print(f"Test failed: {e}")
+ exit(1)
...... [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.70s