resolved RESOLVED UNSUBMITTED PASS · 8 tool calls · 275 s · fastapi/fastapi
🐛 Avoid accessing non-existing "$ref" key for Pydantic v2 compat remapping cf Discussion #14265
| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_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=Nfull{"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} |
| 2 | run_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} |
| 3 | read_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(\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 |
| 4 | read_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\"]], JsonSchfull{"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 |
| 5 | edit_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\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@@ -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} |
| 6 | run_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} |
| 7 | run_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: cdfull{"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}} |
| 8 | run_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} |
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
.. [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