← or20_qwen27b_v2

fastapi_14361

resolved RESOLVED UNSUBMITTED PASS · 8 tool calls · 275 s · fastapi/fastapi

Task input

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

cf Discussion #14265

Tool calls (8)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn '\\$ref' --include=*.py fastapi | head -40"
}
{"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}
2run_command
{
 "command": "git log --oneline -5 && python3 -c \"import pydantic; print(pydantic.VERSION)\""
}
{"status": "ok", "stdout": "e6e231f7 baseline\n013f870d \ud83d\udd27 Configure labeler to exclude files that start from underscore for `lang-all` label (#14213)
full
{"status": "ok", "stdout": "e6e231f7 baseline\n013f870d \ud83d\udd27 Configure labeler to exclude files that start from underscore for `lang-all` label (#14213)\n30504d90 \ud83d\udc77 Add pre-commit config with local script for permalinks (#14398)\ne9101a0b \ud83d\udc77 Add custom pre-commit CI (#14397)\n3e6b1791 \ud83d\udc84 Use font Fira Code to fix display of Rich panels in docs in Windows (#14387)\n2.13.5\n", "exit_code": 0}
3read_file
{
 "filepath": "fastapi/_compat/v2.py",
 "start_line": 150,
 "end_line": 330
}
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": " # ModelField to its JSON Schema.\n return id(self)\n\n\ndef get_annotation_from_field_info(\n
full
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "        # ModelField to its JSON Schema.\n        return id(self)\n\n\ndef get_annotation_from_field_info(\n    annotation: Any, field_info: FieldInfo, field_name: str\n) -> Any:\n    return annotation\n\n\ndef _model_rebuild(model: Type[BaseModel]) -> None:\n    model.model_rebuild()\n\n\ndef _model_dump(\n    model: BaseModel, mode: Literal[\"json\", \"python\"] = \"json\", **kwargs: Any\n) -> Any:\n    return model.model_dump(mode=mode, **kwargs)\n\n\ndef _get_model_config(model: BaseModel) -> Any:\n    return model.model_config\n\n\ndef get_schema_from_model_field(\n    *,\n    field: ModelField,\n    model_name_map: ModelNameMap,\n    field_mapping: Dict[\n        Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue\n    ],\n    separate_input_output_schemas: bool = True,\n) -> Dict[str, Any]:\n    override_mode: Union[Literal[\"validation\"], None] = (\n        None if separate_input_output_schemas else \"validation\"\n    )\n    # This expects that GenerateJsonSchema was already used to generate the definitions\n    json_schema = field_mapping[(field, override_mode or field.mode)]\n    if \"$ref\" not in json_schema:\n        # TODO remove when deprecating Pydantic v1\n        # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207\n        json_schema[\"title\"] = field.field_info.title or field.alias.title().replace(\n            \"_\", \" \"\n        )\n    return json_schema\n\n\ndef get_definitions(\n    *,\n    fields: Sequence[ModelField],\n    model_name_map: ModelNameMap,\n    separate_input_output_schemas: bool = True,\n) -> Tuple[\n    Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue],\n    Dict[str, Dict[str, Any]],\n]:\n    schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)\n    override_mode: Union[Literal[\"validation\"], None] = (\n        None if 
4read_file
{
 "filepath": "fastapi/_compat/v2.py",
 "start_line": 299,
 "end_line": 400
}
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": " ],\n) -> Tuple[\n Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSch
full
{"status": "ok", "filepath": "fastapi/_compat/v2.py", "content": "    ],\n) -> Tuple[\n    Dict[Tuple[ModelField, Literal[\"validation\", \"serialization\"]], JsonSchemaValue],\n    Dict[str, Any],\n]:\n    old_name_to_new_name_map = {}\n    for field_key, schema in field_mapping.items():\n        model = 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            old_name_to_new_name_map=old_name_to_new_name_map,\n        )\n        new_field_mapping[field_key] = new_schema\n\n    new_definitions = {}\n    for key, value in definitions.items():\n        if key in old_name_to_new_name_map:\n            new_key = old_name_to_new_name_map[key]\n        else:\n            new_key = key\n        new_value = _replace_refs(\n            schema=value,\n            old_name_to_new_name_map=old_name_to_new_name_map,\n        )\n        new_definitions[new_key] = new_value\n    return new_field_mapping, new_definitions\n\n\ndef is_scalar_field(field: ModelField) -> bool:\n    from fastapi import params\n\n    return shared.field_annotation_is_scalar(\n        field.field_info.annotation\n    ) and not isinstance(field.field_info, params.Body)\n\n\ndef is_sequence_field(field: ModelField) -> bool:\n    return shared.field_annotation_is_sequence(field.field_info.annotation)\n\n\ndef is_scalar_sequence_field(field: ModelField) -> bool:\n    return shared.field_annotation_is_scalar_sequence(field.field_info.annotation)\n\n\ndef is_bytes_field(field: ModelFiel
5edit_file
{
 "filepath": "fastapi/_compat/v2.py",
 "old_string": "        new_name = model_name_map[model]\n        old_name = schema[\"$ref\"].split(\"/\")[-1]",
 "new_string": "        new_name = model_name_map[model]\n        if \"$ref\" not in schema:\n            continue\n        old_name = schema[\"$ref\"].split(\"/\")[-1]"
}
{"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": "cat > /tmp/check.py <<'EOF'\nfrom typing import Annotated, List\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, Field\n\napp = FastAPI()\n\nclass Item(BaseModel):\n    name: str\n\n@app.post(\"/items\")\ndef create_item(item: Item, count: Annotated[int, Field(gt=0)] = 5):\n    return {\"ok\": True}\n\nschema = app.openapi()\nprint(\"OK\", list(schema[\"components\"][\"schemas\"].keys()))\n\n# also check a list-of-scalar body param\napp2 = FastAPI()\n\n@app2.post(\"/nums\")\ndef nums(nums: Annotated[List[int], Field(min_length=1)]):\n    return {\"ok\": True}\n\nschema2 = app2.openapi()\nprint(\"OK2\", list(schema2[\"components\"][\"schemas\"].keys()))\nEOF\npython3 /tmp/check.py"
}
{"status": "ok", "stdout": "OK ['HTTPValidationError', 'Item', 'ValidationError']\nOK2 ['HTTPValidationError', 'ValidationError']\n", "exit_code": 0}
full
{"status": "ok", "stdout": "OK ['HTTPValidationError', 'Item', 'ValidationError']\nOK2 ['HTTPValidationError', 'ValidationError']\n", "exit_code": 0}
7run_command
{
 "command": "cd /workspace && git stash -q && python3 /tmp/check.py 2>&1 | tail -3; git stash pop -q"
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "/bin/bash: line 0: cd
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "/bin/bash: line 0: cd: /tmp/swe_work/or20_qwen27b_v2/fastapi_14361/a/workspace: No such file or directory\nNo stash entries found.\n", "exit_code": 1}}
8run_command
{
 "command": "git stash -q && python3 /tmp/check.py 2>&1 | tail -3; git stash pop -q && git diff --stat"
}
{"status": "ok", "stdout": "OK ['HTTPValidationError', 'Item', 'ValidationError']\nOK2 ['HTTPValidationError', 'ValidationError']\n fastapi/_compat/v2.py | 2 ++
full
{"status": "ok", "stdout": "OK ['HTTPValidationError', 'Item', 'ValidationError']\nOK2 ['HTTPValidationError', 'ValidationError']\n fastapi/_compat/v2.py | 2 ++\n 1 file changed, 2 insertions(+)\n", "exit_code": 0}

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.48s