resolved RESOLVED UNSUBMITTED PASS · 32 tool calls · 300 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' --include=*.py . || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.' ) | head -40"
} | {"status": "ok", "stdout": "./tests/test_openapi_separate_input_output_schemas.py:27:def get_app_client(separate_input_output_schemas: bool = True) -> TestClienfull{"status": "ok", "stdout": "./tests/test_openapi_separate_input_output_schemas.py:27:def get_app_client(separate_input_output_schemas: bool = True) -> TestClient:\n./tests/test_openapi_separate_input_output_schemas.py:28: app = FastAPI(separate_input_output_schemas=separate_input_output_schemas)\n./tests/test_openapi_separate_input_output_schemas.py:55: client_no = get_app_client(separate_input_output_schemas=False)\n./tests/test_openapi_separate_input_output_schemas.py:68: client_no = get_app_client(separate_input_output_schemas=False)\n./tests/test_openapi_separate_input_output_schemas.py:89: client_no = get_app_client(separate_input_output_schemas=False)\n./tests/test_openapi_separate_input_output_schemas.py:116: client_no = get_app_client(separate_input_output_schemas=False)\n./tests/test_openapi_separate_input_output_schemas.py:360: client = get_app_client(separate_input_output_schemas=False)\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: separate_input_output_schemas: bool = True,\n./fastapi/openapi/utils.py:288: separate_input_output_schemas=separate_input_output_schemas,\n./fastapi/openapi/utils.py:309: separate_input_output_schemas=separate_input_output_schemas,\n./fastapi/openapi/utils.py:326: separate_input_output_schemas=separate |
| 2 | run_command | {
"command": "cat > /tmp/repro.py <<'PY'\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\nfrom fastapi.testclient import TestClient\nimport json\n\nclass Item(BaseModel):\n name: str\n price: float\n\n @computed_field\n @property\n def total(self) -> float:\n return self.price * 1.1\n\napp = FastAPI(separate_input_output_schemas=False)\n\n@app.get(\"/item\", response_model=Item)\ndef get_item():\n return Item(name=\"Test\", price=100.0)\n\nclient = TestClient(app)\n\ndef test_openapi_computed_field():\n response = client.get(\"/openapi.json\")\n schema = response.json()\n \n # Find the Item schema in components/schemas\n schemas = schema.get(\"components\", {}).get(\"schemas\", {})\n item_schema = schemas.get(\"Item\")\n \n if not item_schema:\n print(\"Item schema not found!\")\n return\n \n properties = item_schema.get(\"properties\", {})\n print(f\"Properties: {list(properties.keys())}\")\n \n if \"total\" in properties:\n print(\"SUCCESS: 'total' computed field is present in OpenAPI schema\")\n else:\n print(\"FAILURE: 'total' computed field is MISSING from OpenAPI schema\")\n\nif __name__ == \"__main__\":\n test_openapi_computed_field()\nPY\npython3 /tmp/repro.py\n"
} | {"status": "ok", "stdout": "Properties: ['name', 'price']\nFAILURE: 'total' computed field is MISSING from OpenAPI schema\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Properties: ['name', 'price']\nFAILURE: 'total' computed field is MISSING from OpenAPI schema\n", "stderr": "", "exit_code": 0} |
| 3 | run_command | {
"command": "# 1: let's try with separate_input_output_schemas=True to see if it works\ncat > /tmp/repro.py <<'PY'\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\nfrom fastapi.testclient import TestClient\nimport json\n\nclass Item(BaseModel):\n name: str\n price: float\n\n @computed_field\n @property\n def total(self) -> float:\n return self.price * 1.1\n\napp = FastAPI(separate_input_output_schemas=True)\n\n@app.get(\"/item\", response_model=Item)\ndef get_item():\n return Item(name=\"Test\", price=100.0)\n\nclient = TestClient(app)\n\ndef test_openapi_computed_field():\n response = client.get(\"/openapi.json\")\n schema = response.json()\n \n # Find the Item schema in components/schemas\n schemas = schema.get(\"components\", {}).get(\"schemas\", {})\n item_schema = schemas.get(\"Item\")\n \n if not item_schema:\n print(\"Item schema not found!\")\n return\n \n properties = item_schema.get(\"properties\", {})\n print(f\"Properties: {list(properties.keys())}\")\n \n if \"total\" in properties:\n print(\"SUCCESS: 'total' computed field is present in OpenAPI schema\")\n else:\n print(\"FAILURE: 'total' computed field is MISSING from OpenAPI schema\")\n\nif __name__ == \"__main__\":\n test_openapi_computed_field()\nPY\npython3 /tmp/repro.py\n"
} | {"status": "ok", "stdout": "Properties: ['name', 'price', 'total']\nSUCCESS: 'total' computed field is present in OpenAPI schema\n", "stderr": "", "exit_code": full{"status": "ok", "stdout": "Properties: ['name', 'price', 'total']\nSUCCESS: 'total' computed field is present in OpenAPI schema\n", "stderr": "", "exit_code": 0} |
| 4 | run_command | {
"command": "# 2: Inspect fastapi/openapi/utils.py to see how schemas are generated\nsed -n '250,350p' 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: Difull{"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)\ |
| 5 | run_command | {
"command": "grep -n \"get_openapi_operation_responses\" 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}} |
| 6 | run_command | {
"command": "grep -n \"get_openapi_responses\" 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}} |
| 7 | run_command | {
"command": "# 3: Let's search for where response models are handled in fastapi/openapi/utils.py\ngrep -n \"response_model\" 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}} |
| 8 | run_command | {
"command": "# 4: Let's search for where the response bodies are handled\ngrep -n \"responses\" fastapi/openapi/utils.py"
} | {"status": "ok", "stdout": "27:from fastapi.responses import Response\n35:from starlette.responses import JSONResponse\n337: # responses in Starlette\n343: operfull{"status": "ok", "stdout": "27:from fastapi.responses import Response\n35:from starlette.responses import JSONResponse\n337: # responses in Starlette\n343: operation.setdefault(\"responses\", {}).setdefault(status_code, {})[\n360: operation.setdefault(\"responses\", {}).setdefault(\n365: if route.responses:\n366: operation_responses = operation.setdefault(\"responses\", {})\n370: ) in route.responses.items():\n376: openapi_response = operation_responses.setdefault(\n400: ) or http.client.responses.get(int(additional_status_code))\n412: status in operation[\"responses\"]\n415: operation[\"responses\"][http422] = {\n440: responses_from_routes: List[ModelField] = []\n453: responses_from_routes.append(route.response_field)\n455: responses_from_routes.extend(route.response_fields.values())\n462: body_fields_from_routes + responses_from_routes + request_fields_from_routes\n", "stderr": "", "exit_code": 0} |
| 9 | run_command | {
"command": "# 5: Let's look around line 360 of fastapi/openapi/utils.py\nsed -n '360,410p' fastapi/openapi/utils.py"
} | {"status": "ok", "stdout": " operation.setdefault(\"responses\", {}).setdefault(\n status_code, {}\n ).setdefault(\"content\", {}).setdefault(route_response_medfull{"status": "ok", "stdout": " operation.setdefault(\"responses\", {}).setdefault(\n status_code, {}\n ).setdefault(\"content\", {}).setdefault(route_response_media_type, {})[\n \"schema\"\n ] = response_schema\n if route.responses:\n operation_responses = operation.setdefault(\"responses\", {})\n for (\n additional_status_code,\n additional_response,\n ) in route.responses.items():\n process_response = additional_response.copy()\n process_response.pop(\"model\", None)\n status_code_key = str(additional_status_code).upper()\n if status_code_key == \"DEFAULT\":\n status_code_key = \"default\"\n openapi_response = operation_responses.setdefault(\n status_code_key, {}\n )\n assert isinstance(process_response, dict), (\n \"An additional response must be a dict\"\n )\n field = route.response_fields.get(additional_status_code)\n additional_field_schema: Optional[Dict[str, Any]] = None\n if field:\n additional_field_schema = get_schema_from_model_field(\n field=field,\n model_name_map=model_name_map,\n field_mapping=field_mapping,\n separate_input_output_schemas=separate_input_output_schemas,\n )\n media_type = route_response_media_type or \"application/json\"\n additional_schema = (\n process_response.setdefault(\"content\", {})\n .setdefault(media_type, {})\n |
| 10 | run_command | {
"command": "# 6: I see `response_schema` being assigned. Let's find where it's defined.\nsed -n '340,360p' fastapi/openapi/utils.py"
} | {"status": "ok", "stdout": " if status_code_param is not None:\n if isinstance(status_code_param.default, int):\n status_code = str(status_code_param.default)\nfull{"status": "ok", "stdout": " if status_code_param is not None:\n if isinstance(status_code_param.default, int):\n status_code = str(status_code_param.default)\n operation.setdefault(\"responses\", {}).setdefault(status_code, {})[\n \"description\"\n ] = route.response_description\n if route_response_media_type and is_body_allowed_for_status_code(\n route.status_code\n ):\n response_schema = {\"type\": \"string\"}\n if lenient_issubclass(current_response_class, JSONResponse):\n if route.response_field:\n response_schema = get_schema_from_model_field(\n field=route.response_field,\n model_name_map=model_name_map,\n field_mapping=field_mapping,\n separate_input_output_schemas=separate_input_output_schemas,\n )\n else:\n response_schema = {}\n operation.setdefault(\"responses\", {}).setdefault(\n", "stderr": "", "exit_code": 0} |
| 11 | run_command | {
"command": "# 7: Now let's look at get_schema_from_model_field in fastapi/openapi/utils.py\ngrep -n \"def get_schema_from_model_field\" 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}} |
| 12 | run_command | {
"command": "# 8: Search for get_schema_from_model_field in the whole directory just in case\ngrep -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", "stderr": "", "exit_code": 0} |
| 13 | run_command | {
"command": "# 9: It seems get_schema_from_model_field is in _compat.\n# Let's check fastapi/_compat/v2.py as it's more likely for Pydantic v2\nsed -n '174,230p' fastapi/_compat/v2.py"
} | {"status": "ok", "stdout": "def get_schema_from_model_field(\n *,\n field: ModelField,\n model_name_map: ModelNameMap,\n field_mapping: Dict[\n Tuple[ModelFieldfull{"status": "ok", "stdout": "def get_schema_from_model_field(\n *,\n field: ModelField,\n model_name_map: ModelNameMap,\n field_mapping: Dict[\n Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n ],\n separate_input_output_schemas: bool = True,\n) -> Dict[str, Any]:\n override_mode: Union[Literal[\"validation\"], None] = (\n None if separate_input_output_schemas else \"validation\"\n )\n # This expects that GenerateJsonSchema was already used to generate the definitions\n json_schema = field_mapping[(field, override_mode or field.mode)]\n if \"$ref\" not in json_schema:\n # TODO remove when deprecating Pydantic v1\n # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207\n json_schema[\"title\"] = field.field_info.title or field.alias.title().replace(\n \"_\", \" \"\n )\n return json_schema\n\n\ndef get_definitions(\n *,\n fields: Sequence[ModelField],\n model_name_map: ModelNameMap,\n separate_input_output_schemas: bool = True,\n) -> Tuple[\n Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue],\n Dict[str, Dict[str, Any]],\n]:\n schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)\n override_mode: Union[Literal[\"validation\"], None] = (\n None if separate_input_output_schemas else \"validation\"\n )\n validation_fields = [field for field in fields if field.mode == \"validation\"]\n serialization_fields = [field for field in fields if field.mode == \"serialization\"]\n flat_validation_models = get_flat_models_from_fields(\n validation_fields, known_models=set()\n )\n flat_serialization_models = get_flat_models_from_fields(\n serialization_fields, known_models=set()\n )\n flat_validation_model_fields = [\n ModelField(\n field_info=FieldInfo(annotation=model),\n |
| 14 | run_command | {
"command": "# 10: Let's see the GenerateJsonSchema class in fastapi/_compat/v2.py\ngrep -n \"class GenerateJsonSchema\" 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}} |
| 15 | run_command | {
"command": "# 11: search for GenerateJsonSchema in the whole directory\ngrep -rn \"class GenerateJsonSchema\" ."
} | {"status": "ok", "stdout": "./fastapi/_compat/v1.py:149:class GenerateJsonSchema:\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "./fastapi/_compat/v1.py:149:class GenerateJsonSchema:\n", "stderr": "", "exit_code": 0} |
| 16 | run_command | {
"command": "# 12: let's check fastapi/_compat/v1.py\nsed -n '149,300p' fastapi/_compat/v1.py"
} | {"status": "ok", "stdout": "class GenerateJsonSchema:\n ref_template: str\n\n\nclass PydanticSchemaGenerationError(Exception):\n pass\n\n\nRequestErrorModel: Tyfull{"status": "ok", "stdout": "class GenerateJsonSchema:\n ref_template: str\n\n\nclass PydanticSchemaGenerationError(Exception):\n pass\n\n\nRequestErrorModel: Type[BaseModel] = create_model(\"Request\")\n\n\ndef with_info_plain_validator_function(\n function: Callable[..., Any],\n *,\n ref: Union[str, None] = None,\n metadata: Any = None,\n serialization: Any = None,\n) -> Any:\n return {}\n\n\ndef get_model_definitions(\n *,\n flat_models: Set[Union[Type[BaseModel], Type[Enum]]],\n model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str],\n) -> Dict[str, Any]:\n definitions: Dict[str, Dict[str, Any]] = {}\n for model in flat_models:\n m_schema, m_definitions, m_nested_models = model_process_schema(\n model, model_name_map=model_name_map, ref_prefix=REF_PREFIX\n )\n definitions.update(m_definitions)\n model_name = model_name_map[model]\n definitions[model_name] = m_schema\n for m_schema in definitions.values():\n if \"description\" in m_schema:\n m_schema[\"description\"] = m_schema[\"description\"].split(\"\\f\")[0]\n return definitions\n\n\ndef is_pv1_scalar_field(field: ModelField) -> bool:\n from fastapi import params\n\n field_info = field.field_info\n if not (\n field.shape == SHAPE_SINGLETON\n and not lenient_issubclass(field.type_, BaseModel)\n and not lenient_issubclass(field.type_, dict)\n and not shared.field_annotation_is_sequence(field.type_)\n and not is_dataclass(field.type_)\n and not isinstance(field_info, params.Body)\n ):\n return False\n if field.sub_fields:\n if not all(is_pv1_scalar_field(f) for f in field.sub_fields):\n return False\n return True\n\n\ndef is_pv1_scalar_sequence_field(field: ModelField) -> bool:\n if (field.shape in sequence_shapes) and not lenient_issubclass(\n field.type_, BaseModel\n ):\n if field.sub_fields is |
| 17 | run_command | {
"command": "# 13: I'm confused. GenerateJsonSchema was declared but not defined in v1.py (it was empty in sed output).\n# Let's search for GenerateJsonSchema definition again.\ngrep -rn \"class GenerateJsonSchema\" ."
} | {"status": "ok", "stdout": "./fastapi/_compat/v1.py:149:class GenerateJsonSchema:\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "./fastapi/_compat/v1.py:149:class GenerateJsonSchema:\n", "stderr": "", "exit_code": 0} |
| 18 | run_command | {
"command": "# 14: Let's check around line 149 of fastapi/_compat/v1.py again.\nsed -n '140,160p' fastapi/_compat/v1.py"
} | {"status": "ok", "stdout": " SHAPE_LIST: list,\n SHAPE_SET: set,\n SHAPE_TUPLE: tuple,\n SHAPE_SEQUENCE: list,\n SHAPE_TUPLE_ELLIPSIS: list,\n}\n\n\n@dataclass\full{"status": "ok", "stdout": " SHAPE_LIST: list,\n SHAPE_SET: set,\n SHAPE_TUPLE: tuple,\n SHAPE_SEQUENCE: list,\n SHAPE_TUPLE_ELLIPSIS: list,\n}\n\n\n@dataclass\nclass GenerateJsonSchema:\n ref_template: str\n\n\nclass PydanticSchemaGenerationError(Exception):\n pass\n\n\nRequestErrorModel: Type[BaseModel] = create_model(\"Request\")\n\n\ndef with_info_plain_validator_function(\n", "stderr": "", "exit_code": 0} |
| 19 | run_command | {
"command": "# 15: Wait, I see it's just a dataclass. Where is it used?\ngrep -rn \"GenerateJsonSchema\" ."
} | {"status": "ok", "stdout": "./fastapi/_compat/v1.py:149:class GenerateJsonSchema:\n./fastapi/_compat/v2.py:31:from pydantic.json_schema import GenerateJsonSchemfull{"status": "ok", "stdout": "./fastapi/_compat/v1.py:149:class GenerateJsonSchema:\n./fastapi/_compat/v2.py:31:from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema\n./fastapi/_compat/v2.py:186: # This expects that GenerateJsonSchema was already used to generate the definitions\n./fastapi/_compat/v2.py:206: schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)\n", "stderr": "", "exit_code": 0} |
| 20 | run_command | {
"command": "# 16: Okay, it's imported from pydantic.json_schema in v2.py.\n# Let's go back to fastapi/_compat/v2.py and look at get_definitions.\nsed -n '195,230p' fastapi/_compat/v2.py"
} | {"status": "ok", "stdout": "\n\ndef get_definitions(\n *,\n fields: Sequence[ModelField],\n model_name_map: ModelNameMap,\n separate_input_output_schemas: bool full{"status": "ok", "stdout": "\n\ndef get_definitions(\n *,\n fields: Sequence[ModelField],\n model_name_map: ModelNameMap,\n separate_input_output_schemas: bool = True,\n) -> Tuple[\n Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue],\n Dict[str, Dict[str, Any]],\n]:\n schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)\n override_mode: Union[Literal[\"validation\"], None] = (\n None if separate_input_output_schemas else \"validation\"\n )\n validation_fields = [field for field in fields if field.mode == \"validation\"]\n serialization_fields = [field for field in fields if field.mode == \"serialization\"]\n flat_validation_models = get_flat_models_from_fields(\n validation_fields, known_models=set()\n )\n flat_serialization_models = get_flat_models_from_fields(\n serialization_fields, known_models=set()\n )\n flat_validation_model_fields = [\n ModelField(\n field_info=FieldInfo(annotation=model),\n name=model.__name__,\n mode=\"validation\",\n )\n for model in flat_validation_models\n ]\n flat_serialization_model_fields = [\n ModelField(\n field_info=FieldInfo(annotation=model),\n name=model.__name__,\n mode=\"serialization\",\n", "stderr": "", "exit_code": 0} |
| 21 | run_command | {
"command": "# 17: let's see the rest of get_definitions in fastapi/_compat/v2.py\nsed -n '231,300p' fastapi/_compat/v2.py"
} | {"status": "ok", "stdout": " )\n for model in flat_serialization_models\n ]\n flat_model_fields = flat_validation_model_fields + flat_serialization_model_fieldsfull{"status": "ok", "stdout": " )\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_schema[\"$ref\"] = REF_TEMPLATE.format(model=new_name)\n continue\n if isinstance(value, dict):\n new_schema[key] = _replace_refs(\n schema=value,\n old_name_to_new_name_map=old_name_to_new_name_map,\n )\n elif isinstance(value, list):\n new_value = []\n fo |
| 22 | run_command | {
"command": "# 18: In get_definitions, if separate_input_output_schemas=False, override_mode is \"validation\".\n# And inputs are passed to schema_generator.generate_definitions:\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# If override_mode is \"validation\", then all fields are processed as \"validation\".\n# In Pydantic v2, computed fields are only present in \"serialization\" mode, not \"validation\" mode.\n# So if separate_input_output_schemas=False, computed fields are omitted because we use \"validation\" mode.\n\n# Let's verify this hypothesis.\n# In Pydantic v2, computed fields are for output.\n# When separate_input_output_schemas=True, we probably use both validation and serialization modes.\n# Let's look at get_definitions again.\n# If separate_input_output_schemas=True, override_mode is None.\n# Then the second element of the tuple in inputs is `field.mode`.\n# ModelField has a mode (\"validation\" or \"serialization\").\n# So it uses both.\n# But if separate_input_output_schemas=False, it uses \"validation\" for everything.\n\n# To fix this, we should probably use \"serialization\" mode if we want the output schema.\n# But the input schema should still use \"validation\" mode.\n# The issue is that when separate_input_output_schemas=False, we are using a single schema for both.\n# FastAPI seems to choose \"validation\" mode as the default for the shared schema.\n# However, the shared schema should ideally be a merge of both, or at least include computed fields if it's used for output.\n\n# Actually, if separate_input_output_schemas=False, the \"validation\" mode schema is used for both.\n# But computed fields are NOT part of the validation schema.\n\n# Let's check if we can change \"validation\" to \"serialization\" when separate_input_output_schemas=False.\n# No, because then we might lose some validation-specific info.\n\n# Wait, if separate_input_output_schemas=False, we want a schema that can be used for both.\n# In Pydantic v2, the \"serialization\" schema usually includes everything that the \"validation\" schema does, plus computed fields.\n\n# Let's try changing \"validation\" to \"serialization\" in fastapi/_compat/v2.py.\n# But wait, the code says:\n# override_mode: Union[Literal[\"validation\"], None] = (\n# None if separate_input_output_schemas else \"validation\"\n# )\n# Let's try changing \"validation\" to \"serialization\".\n# But \"serialization\" is not a Literal[\"validation\"] in the type hint.\n# Let's check the type hint for override_mode.\n# override_mode: Union[Literal[\"validation\"], None]\n\n# Let's see if we can use \"serialization\" as override_mode.\n# The type hint might just be too restrictive.\n# The inputs are passed to schema_generator.generate_definitions.\n# Let's see if pydantic's GenerateJsonSchema supports \"serialization\".\n# It likely does since it's used for serialization_fields.\n\n# Let's try to change \"validation\" to \"serialization\" and see if it fixes the repro.\n# But before that, let's check if \"serialization\" mode includes all validation fields.\n# In Pydantic v2, it generally does.\n\n# Let's try the change.\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 23 | run_command | {
"command": "# 19: I will change \"validation\" to \"serialization\" in fastapi/_compat/v2.py in two places.\n# 1. in get_schema_from_model_field\n# 2. in get_definitions\n\n# Let's check the lines again.\n# get_schema_from_model_field: line 180\n# get_definitions: line 204\n\n# Actually, the type hint for override_mode in get_schema_from_model_field is:\n# override_mode: Union[Literal[\"validation\"], None]\n# And in get_definitions:\n# override_mode: Union[Literal[\"validation\"], None]\n\n# I should also update the type hints if I change the value.\n\n# Wait, let's see if there's a better way.\n# If separate_input_output_schemas=False, we are essentially saying \"I want one schema for both\".\n# If we use \"serialization\" mode, it should include computed fields and all regular fields.\n# If we use \"validation\" mode, it only includes regular fields.\n# So \"serialization\" is what we want for the combined schema.\n\n# Let's apply the change.\npython3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/_compat/v2.py\")\ns = p.read_text()\n\n# Fix get_schema_from_model_field\nold1 = \"\"\" override_mode: Union[Literal[\"validation\"], None] = (\n None if separate_input_output_schemas else \"validation\"\n )\"\"\"\nnew1 = \"\"\" override_mode: Union[Literal[\"validation\", \"serialization\"], None] = (\n None if separate_input_output_schemas else \"serialization\"\n )\"\"\"\ns = s.replace(old1, new1)\n\n# Fix get_definitions\nold2 = \"\"\" override_mode: Union[Literal[\"validation\"], None] = (\n None if separate_input_output_schemas else \"validation\"\n )\"\"\"\n# This is the same as old1, but in get_definitions.\n# Since it's exactly the same string, I should use replace with a count or be more specific.\n# Let's use a more specific replace.\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 24 | run_command | {
"command": "# 20: let's use sed or a more precise python script to replace the two occurrences.\n# I'll search for the lines and replace them based on their context.\n\npython3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/_compat/v2.py\")\ns = p.read_text()\nlines = s.splitlines()\n\n# Find indices of \"override_mode: Union[Literal[\"validation\"], None] = (\"\nindices = [i for i, line in enumerate(lines) if 'override_mode: Union[Literal[\"validation\"], None] = (' in line]\n\n# We expect two indices.\nif len(indices) != 2:\n print(f\"Expected 2 occurrences, found {len(indices)}\")\n exit(1)\n\n# Replace first one (get_schema_from_model_field)\nlines[indices[0]] = ' override_mode: Union[Literal[\"validation\", \"serialization\"], None] = ('\nlines[indices[0]+1] = ' None if separate_input_output_schemas else \"serialization\"'\n\n# Replace second one (get_definitions)\nlines[indices[1]] = ' override_mode: Union[Literal[\"validation\", \"serialization\"], None] = ('\nlines[indices[1]+1] = ' None if separate_input_output_schemas else \"serialization\"'\n\np.write_text(\"\\n\".join(lines))\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 25 | run_command | {
"command": "# 21: Now let's run the repro script.\npython3 /tmp/repro.py"
} | {"status": "ok", "stdout": "Properties: ['name', 'price', 'total']\nSUCCESS: 'total' computed field is present in OpenAPI schema\n", "stderr": "", "exit_code": full{"status": "ok", "stdout": "Properties: ['name', 'price', 'total']\nSUCCESS: 'total' computed field is present in OpenAPI schema\n", "stderr": "", "exit_code": 0} |
| 26 | run_command | {
"command": "# 22: The repro now passes. Let's check if it broke anything else.\n# The original issue was specifically about computed fields when separate_input_output_schemas=False.\n# Now let's check if separate_input_output_schemas=True still works as expected.\n# My repro script already checked that (it's a separate test case).\n# Let's run the repro script again to be sure.\npython3 /tmp/repro.py"
} | {"status": "ok", "stdout": "Properties: ['name', 'price', 'total']\nSUCCESS: 'total' computed field is present in OpenAPI schema\n", "stderr": "", "exit_code": full{"status": "ok", "stdout": "Properties: ['name', 'price', 'total']\nSUCCESS: 'total' computed field is present in OpenAPI schema\n", "stderr": "", "exit_code": 0} |
| 27 | run_command | {
"command": "# 23: wait, I should check if I broke the \"separate_input_output_schemas=True\" case.\n# My repro script only tests the False case now (the one I run at the end).\n# Let me run a script that tests both.\ncat > /tmp/repro_both.py <<'PY'\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, computed_field\nfrom fastapi.testclient import TestClient\nimport json\n\nclass Item(BaseModel):\n name: str\n price: float\n\n @computed_field\n @property\n def total(self) -> float:\n return self.price * 1.1\n\ndef test_openapi(separate: bool):\n app = FastAPI(separate_input_output_schemas=separate)\n\n @app.get(\"/item\", response_model=Item)\n def get_item():\n return Item(name=\"Test\", price=100.0)\n\n client = TestClient(app)\n response = client.get(\"/openapi.json\")\n schema = response.json()\n properties = schema.get(\"components\", {}).get(\"schemas\", {}).get(\"Item\", {}).get(\"properties\", {})\n print(f\"separate={separate}, properties={list(properties.keys())}\")\n return list(properties.keys())\n\nif __name__ == \"__main__\":\n res_false = test_openapi(False)\n res_true = test_openapi(True)\n \n assert \"total\" in res_false, f\"Computed field missing when separate=False: {res_false}\"\n assert \"total\" in res_true, f\"Computed field missing when separate=True: {res_true}\"\n print(\"ALL TESTS PASSED\")\nPY\npython3 /tmp/repro_both.py\n"
} | {"status": "ok", "stdout": "separate=False, properties=['name', 'price', 'total']\nseparate=True, properties=['name', 'price', 'total']\nALL TESTS PASSED\n", "sfull{"status": "ok", "stdout": "separate=False, properties=['name', 'price', 'total']\nseparate=True, properties=['name', 'price', 'total']\nALL TESTS PASSED\n", "stderr": "", "exit_code": 0} |
| 28 | run_command | {
"command": "# 24: Let's check if we have any existing tests for separate_input_output_schemas.\n# I saw tests/test_openapi_separate_input_output_schemas.py earlier.\n# Let's run it.\npython -m pytest tests/test_openapi_separate_input_output_schemas.py\n"
} | {"status": "error", "error_type": "CommandError", "error_message": "============================= test session starts ==============================\nplatform dfull{"status": "error", "error_type": "CommandError", "error_message": "============================= test session starts ==============================\nplatform darwin -- Python 3.13.15, pytest-9.1.1, pluggy-1.6.0\ncodspeed: 5.0.3 (disabled, mode: walltime, callgraph: not supported, timer_resolution: 41.7ns)\nrootdir: /private/workspace\nconfigfile: pytest.ini (WARNING: ignoring pytest config in pyproject.toml!)\nplugins: cov-7.1.0, xdist-3.8.0, timeout-2.4.0, codspeed-5.0.3, sugar-1.1.1, anyio-4.15.1, inline-snapshot-0.35.4\ncollected 6 items\n\ntests/test_openapi_separate_input_output_schemas.py .....F [100%]\n\n=================================== FAILURES ===================================\n_______________________ test_openapi_schema_no_separate ________________________\n\n @needs_pydanticv2\n def test_openapi_schema_no_separate():\n client = get_app_client(separate_input_output_schemas=False)\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 \"/items/\": {\n \"get\": {\n \"summary\": \"Read Items\",\n \"operationId\": \"read_items_items__get\",\n \"responses\": {\n \"200\": {\n \"description\": \"Successful Response\",\n \"content\": {\n \"application/json\": {\n \"schema\": {\n \"items\": {\"$ref\": \"#/components/schemas/Item\"},\n \"type\": \"array\",\n \"title\": \"Response Read Items Items Get\",\n |
| 29 | run_command | {
"command": "# 25: The test failed because the expected JSON changed.\n# This is expected since I changed the default schema for separate_input_output_schemas=False.\n# I should check if the change is correct for the tests.\n# The failing test is `test_openapi_schema_no_separate`.\n# It expects the schema to be exactly what it was.\n# But the issue is that computed fields WERE missing and they SHOULD be present.\n# So the test itself might need to be updated to expect computed fields if the model has any.\n# Let's see the model used in `tests/test_openapi_separate_input_output_schemas.py`.\n\nsed -n '1,100p' tests/test_openapi_separate_input_output_schemas.py"
} | {"status": "ok", "stdout": "from typing import List, Optional\n\nfrom fastapi import FastAPI\nfrom fastapi.testclient import TestClient\nfrom inline_snapshot imfull{"status": "ok", "stdout": "from typing import List, Optional\n\nfrom fastapi import FastAPI\nfrom fastapi.testclient import TestClient\nfrom inline_snapshot import snapshot\nfrom pydantic import BaseModel\n\nfrom .utils import PYDANTIC_V2, needs_pydanticv2\n\n\nclass SubItem(BaseModel):\n subname: str\n sub_description: Optional[str] = None\n tags: List[str] = []\n if PYDANTIC_V2:\n model_config = {\"json_schema_serialization_defaults_required\": True}\n\n\nclass Item(BaseModel):\n name: str\n description: Optional[str] = None\n sub: Optional[SubItem] = None\n if PYDANTIC_V2:\n model_config = {\"json_schema_serialization_defaults_required\": True}\n\n\ndef get_app_client(separate_input_output_schemas: bool = True) -> TestClient:\n app = FastAPI(separate_input_output_schemas=separate_input_output_schemas)\n\n @app.post(\"/items/\", responses={402: {\"model\": Item}})\n def create_item(item: Item) -> Item:\n return item\n\n @app.post(\"/items-list/\")\n def create_item_list(item: List[Item]):\n return item\n\n @app.get(\"/items/\")\n def read_items() -> List[Item]:\n return [\n Item(\n name=\"Portal Gun\",\n description=\"Device to travel through the multi-rick-verse\",\n sub=SubItem(subname=\"subname\"),\n ),\n Item(name=\"Plumbus\"),\n ]\n\n client = TestClient(app)\n return client\n\n\ndef test_create_item():\n client = get_app_client()\n client_no = get_app_client(separate_input_output_schemas=False)\n response = client.post(\"/items/\", json={\"name\": \"Plumbus\"})\n response2 = client_no.post(\"/items/\", json={\"name\": \"Plumbus\"})\n assert response.status_code == response2.status_code == 200, response.text\n assert (\n response.json()\n == response2.json()\n == {\"name\": \"Plumbus\", \"description\": None, \"sub\": None}\n )\n\n\ndef test_create_ |
| 30 | run_command | {
"command": "# 26: The models in this test do NOT have computed fields.\n# If the models don't have computed fields, using \"serialization\" instead of \"validation\"\n# should NOT change the schema.\n# Let's check why it changed.\n# If I use \"serialization\" mode, does it change the schema for regular fields?\n# Let's test this.\n\ncat > /tmp/test_diff.py <<'PY'\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel\nfrom fastapi.testclient import TestClient\nimport json\n\nclass Item(BaseModel):\n name: str\n description: str = \"default\"\n\ndef test_separate(sep):\n app = FastAPI(separate_input_output_schemas=sep)\n @app.get(\"/item\", response_model=Item)\n def get_item():\n return Item(name=\"test\")\n client = TestClient(app)\n response = client.get(\"/openapi.json\")\n return response.json()[\"components\"][\"schemas\"][\"Item\"]\n\nif __name__ == \"__main__\":\n s_true = test_separate(True)\n s_false = test_separate(False)\n print(f\"Separate=True: {s_true}\")\n print(f\"Separate=False: {s_false}\")\n print(f\"Equal: {s_true == s_false}\")\nPY\npython3 /tmp/test_diff.py\n"
} | {"status": "ok", "stdout": "Separate=True: {'properties': {'name': {'type': 'string', 'title': 'Name'}, 'description': {'type': 'string', 'title': 'Description'full{"status": "ok", "stdout": "Separate=True: {'properties': {'name': {'type': 'string', 'title': 'Name'}, 'description': {'type': 'string', 'title': 'Description', 'default': 'default'}}, 'type': 'object', 'required': ['name'], 'title': 'Item'}\nSeparate=False: {'properties': {'name': {'type': 'string', 'title': 'Name'}, 'description': {'type': 'string', 'title': 'Description', 'default': 'default'}}, 'type': 'object', 'required': ['name'], 'title': 'Item'}\nEqual: True\n", "stderr": "", "exit_code": 0} |
| 31 | run_command | {
"command": "# 27: Wait, if they are equal, why did the test fail?\n# Let's look at the failure again.\n# The failure was in `test_openapi_schema_no_separate`.\n# It compares the entire `response.json()` to a large dictionary.\n# Let's see what the difference was.\n\n# I'll run the test and capture the diff.\npython -m pytest tests/test_openapi_separate_input_output_schemas.py > /tmp/test_out.txt 2>&1\ncat /tmp/test_out.txt | grep -A 20 \"FAILURES\"\n"
} | {"status": "ok", "stdout": "=================================== FAILURES ===================================\n_______________________ test_openapi_schema_no_sepfull{"status": "ok", "stdout": "=================================== FAILURES ===================================\n_______________________ test_openapi_schema_no_separate ________________________\n\n @needs_pydanticv2\n def test_openapi_schema_no_separate():\n client = get_app_client(separate_input_output_schemas=False)\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 \"/items/\": {\n \"get\": {\n \"summary\": \"Read Items\",\n \"operationId\": \"read_items_items__get\",\n \"responses\": {\n \"200\": {\n \"description\": \"Successful Response\",\n \"content\": {\n \"application/json\": {\n", "stderr": "", "exit_code": 0} |
| 32 | run_command | {
"command": "# 28: The diff was truncated. Let's use `pytest` with a more detailed output or just use `json.dumps` to compare.\n# Actually, I can just run the test and see the full diff by using `pytest -v`.\n# But the diff is too long.\n\n# Let's try to see if my change to \"serialization\" mode changed anything for models WITHOUT computed fields.\n# I already did that with /tmp/test_diff.py and it said Equal: True.\n\n# Wait, I might have changed something in the type of the schema.\n# Let's check if I missed anything.\n# In get_definitions:\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# If separate_input_output_schemas=False, override_mode is \"serialization\".\n# So we use \"serialization\" mode for everything.\n\n# Let's see if there are any other places where I should have used \"validation\" or \"serialization\".\n\n# Actually, let me try to run the failing test and print the actual vs expected.\n# I'll modify the test file temporarily.\nsed -i 's/assert response.json() == {/print(\"ACTUAL:\", json.dumps(response.json(), indent=2)); assert response.json() == {/' tests/test_openapi_separate_input_output_schemas.py\n# Wait, I need to import json in the test file.\nsed -i '1i import json' tests/test_openapi_separate_input_output_schemas.py\npython -m pytest tests/test_openapi_separate_input_output_schemas.py\n"
} | {"status": "error", "error_type": "CommandError", "error_message": "sed: 1: \"tests/test_openapi_sepa ...\": undefined label 'ests/test_openapi_separate_input_ofull{"status": "error", "error_type": "CommandError", "error_message": "sed: 1: \"tests/test_openapi_sepa ...\": undefined label 'ests/test_openapi_separate_input_output_schemas.py'\nsed: 1: \"tests/test_openapi_sepa ...\": undefined label 'ests/test_openapi_separate_input_output_schemas.py'\n", "details": {"stdout": "============================= test session starts ==============================\nplatform darwin -- Python 3.13.15, pytest-9.1.1, pluggy-1.6.0\ncodspeed: 5.0.3 (disabled, mode: walltime, callgraph: not supported, timer_resolution: 41.7ns)\nrootdir: /private/workspace\nconfigfile: pytest.ini (WARNING: ignoring pytest config in pyproject.toml!)\nplugins: cov-7.1.0, xdist-3.8.0, timeout-2.4.0, codspeed-5.0.3, sugar-1.1.1, anyio-4.15.1, inline-snapshot-0.35.4\ncollected 6 items\n\ntests/test_openapi_separate_input_output_schemas.py .....F [100%]\n\n=================================== FAILURES ===================================\n_______________________ test_openapi_schema_no_separate ________________________\n\n @needs_pydanticv2\n def test_openapi_schema_no_separate():\n client = get_app_client(separate_input_output_schemas=False)\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 \"/items/\": {\n \"get\": {\n \"summary\": \"Read Items\",\n \"operationId\": \"read_items_items__get\",\n \"responses\": {\n \"200\": {\n \"description\": \"Successful Response\",\n \"content\": {\n \"application/json\": {\n \"schema\": {\n |
diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py
index 3d91814c..824f4a1d 100644
--- a/fastapi/_compat/v2.py
+++ b/fastapi/_compat/v2.py
@@ -180,8 +180,8 @@ def get_schema_from_model_field(
],
separate_input_output_schemas: bool = True,
) -> Dict[str, Any]:
- override_mode: Union[Literal["validation"], None] = (
- None if separate_input_output_schemas else "validation"
+ override_mode: Union[Literal["validation", "serialization"], None] = (
+ None if separate_input_output_schemas else "serialization"
)
# This expects that GenerateJsonSchema was already used to generate the definitions
json_schema = field_mapping[(field, override_mode or field.mode)]
@@ -204,8 +204,8 @@ def get_definitions(
Dict[str, Dict[str, Any]],
]:
schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)
- override_mode: Union[Literal["validation"], None] = (
- None if separate_input_output_schemas else "validation"
+ override_mode: Union[Literal["validation", "serialization"], None] = (
+ None if separate_input_output_schemas else "serialization"
)
validation_fields = [field for field in fields if field.mode == "validation"]
serialization_fields = [field for field in fields if field.mode == "serialization"]
@@ -483,4 +483,4 @@ def get_flat_models_from_fields(
def get_long_model_name(model: TypeModelOrEnum) -> str:
- return f"{model.__module__}__{model.__qualname__}".replace(".", "__")
+ return f"{model.__module__}__{model.__qualname__}".replace(".", "__")
\ No newline at end of file
...... [100%]
=============================== warnings summary ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.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.53s