← or20_submission

fastapi_14361

resolved RESOLVED PASS · 6 tool calls · 14 s · fastapi/fastapi

Task input

🐛 Avoid accessing non-existing "$ref" key for Pydantic v2 compat remapping

cf Discussion #14265

Tool calls (7)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"\\$ref\" fastapi | grep \"remapping\" -A 5 -B 5"
}
{"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}}
2run_command
{
 "command": "grep -rn \"\\$ref\" fastapi | head -n 20"
}
{"status": "ok", "stdout": "fastapi/openapi/models.py:103: ref: str = Field(alias=\"$ref\")\nfastapi/openapi/models.py:138: ref: Optional[str] = Field(default=N
full
{"status": "ok", "stdout": "fastapi/openapi/models.py:103:    ref: str = Field(alias=\"$ref\")\nfastapi/openapi/models.py:138:    ref: Optional[str] = Field(default=None, alias=\"$ref\")\nfastapi/openapi/models.py:322:    ref: Optional[str] = Field(default=None, alias=\"$ref\")\nfastapi/openapi/utils.py:63:            \"items\": {\"$ref\": REF_PREFIX + \"ValidationError\"},\nfastapi/openapi/utils.py:419:                            \"schema\": {\"$ref\": REF_PREFIX + \"HTTPValidationError\"}\nfastapi/_compat/v2.py:188:    if \"$ref\" not in json_schema:\nfastapi/_compat/v2.py:264:        if key == \"$ref\":\nfastapi/_compat/v2.py:265:            value = schema[\"$ref\"]\nfastapi/_compat/v2.py:267:                ref_name = schema[\"$ref\"].split(\"/\")[-1]\nfastapi/_compat/v2.py:270:                    new_schema[\"$ref\"] = REF_TEMPLATE.format(model=new_name)\nfastapi/_compat/v2.py:310:        old_name = schema[\"$ref\"].split(\"/\")[-1]\n", "exit_code": 0}
3read_file
{
 "filepath": "fastapi/_compat/v2.py",
 "start_line": 250,
 "end_line": 320
}
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": " model_name_map=model_name_map,\n definitions=definitions, # type: ignore[arg-type]\n field_ma
full
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "        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            for item in value:\n                if isinstance(item, dict):\n                    new_item = _replace_refs(\n                        schema=item,\n                        old_name_to_new_name_map=old_name_to_new_name_map,\n                    )\n                    new_value.append(new_item)\n\n                else:\n                    new_value.append(item)\n            new_schema[key] = new_value\n    return new_schema\n\n\ndef _remap_definitions_and_field_mappings(\n    *,\n    model_name_map: ModelNameMap,\n    definitions: Dict[str, Any],\n    field_mapping: Dict[\n        Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n    ],\n) -> Tuple[\n    Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue],\n    Dict[str, Any],\n]:\n    old_name_to_new_name_map = {}\n    for field_key, schema in
4read_file
{
 "end_line": 320,
 "filepath": "fastapi/_compat/v2.py",
 "start_line": 300
}
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": ") -> Tuple[\n Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaVa
full
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": ") -> Tuple[\n    Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue],\n    Dict[str, Any],\n]:\n    old_name_to_new_name_map = {}\n    for field_key, schema in field_mapping.items():\n        model = field_key[0].type_\n        if model not in model_name_map:\n            continue\n        new_name = model_name_map[model]\n        old_name = schema[\"$ref\"].split(\"/\")[-1]\n        if old_name in {f\"{new_name}-Input\", f\"{new_name}-Output\"}:\n            continue\n        old_name_to_new_name_map[old_name] = new_name\n\n    new_field_mapping: Dict[\n        Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n    ] = {}\n    for field_key, schema in field_mapping.items():\n        new_schema = _replace_refs(\n            schema=schema,\n", "start_line": 300, "end_line": 320, "total_lines": 479, "is_truncated": false}
5edit_file
{
 "filepath": "fastapi/_compat/v2.py",
 "new_string": "        new_name = model_name_map[model]\n        if \"$ref\" not in schema:\n            continue\n        old_name = schema[\"$ref\"].split(\"/\")[-1]\n        if old_name in {f\"{new_name}-Input\", f\"{new_name}-Output\"}:",
 "old_string": "        new_name = model_name_map[model]\n        old_name = schema[\"$ref\"].split(\"/\")[-1]\n        if old_name in {f\"{new_name}-Input\", f\"{new_name}-Output\"}:"
}
{"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@@ -307,6 +307,8 @@\n         if model not in model_name_map:\n             continue\n         new_name = model_name_map[model]\n+        if \"$ref\" not in schema:\n+            continue\n         old_name = schema[\"$ref\"].split(\"/\")[-1]\n         if old_name in {f\"{new_name}-Input\", f\"{new_name}-Output\"}:\n             continue\n", "is_truncated": false}
6run_command
{
 "command": "python -m pytest tests/test_schema_ref_pydantic_v2.py -q -x"
}
{"status": "ok", "stdout": ".. [100%]\n=============================== warnings summary ===============================\n../../../../../../../Users/jp/repos/kag
full
{"status": "ok", "stdout": "..                                                                       [100%]\n=============================== warnings summary ===============================\n../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45\n  /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.\n    _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n2 passed, 1 warning in 0.40s\n", "exit_code": 0}
7submit_patch
[]
{"status": "ok", "patch_size": 512, "files_changed": 1}
full
{"status": "ok", "patch_size": 512, "files_changed": 1}

Patch

diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py
index 5cd49343..51460d1d 100644
--- a/fastapi/_compat/v2.py
+++ b/fastapi/_compat/v2.py
@@ -307,6 +307,8 @@ def _remap_definitions_and_field_mappings(
         if model not in model_name_map:
             continue
         new_name = model_name_map[model]
+        if "$ref" not in schema:
+            continue
         old_name = schema["$ref"].split("/")[-1]
         if old_name in {f"{new_name}-Input", f"{new_name}-Output"}:
             continue

Test output

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