Spaces:
Running
Running
| # FixFlow Sample Output β FastAPI Bug Analysis | |
| **Issue:** [FastAPI response_model doesn't strip extra fields when using Pydantic v2](https://github.com/tiangolo/fastapi/issues/10876) | |
| **Repository:** https://github.com/tiangolo/fastapi | |
| **Analysis Time:** 87.3s | |
| --- | |
| ## π Step 1: Bug Summary | |
| ### π Error Message | |
| > "When using `response_model` in FastAPI with Pydantic v2, extra fields defined in the response model are NOT stripped from the response. This breaks the behavior expected from the `response_model_exclude_unset` pattern." | |
| ### β Expected Behavior | |
| When a route has a `response_model` set, FastAPI should filter the response to only include fields defined in that model, stripping any additional fields from the underlying return value. | |
| ### β Actual Behavior | |
| Extra fields from the returned object are included in the JSON response even when a `response_model` is specified. This is a regression from Pydantic v1 behavior. | |
| ### π Reproduction Steps | |
| 1. Install `fastapi>=0.100.0` with `pydantic>=2.0.0` | |
| 2. Define a route: `@app.get("/users/{id}", response_model=UserOut)` | |
| 3. Return a `UserDB` object with extra fields not in `UserOut` | |
| 4. Observe: response includes the extra fields | |
| ### π― Affected Components | |
| - `fastapi/routing.py` β route handler serialization logic | |
| - `fastapi/_compat.py` β Pydantic v1/v2 compatibility layer | |
| - `fastapi/encoders.py` β JSON encoding pipeline | |
| ### π Key Technical Clues | |
| - Introduced after Pydantic v2 migration | |
| - `_get_value()` in `fastapi/_compat.py` changed behavior for model instances | |
| - The `model_dump(exclude_unset=True)` call may not be filtering correctly | |
| ### π‘ Hypothesis | |
| The Pydantic v2 compatibility layer in `_compat.py` is not correctly calling `model_dump()` with the `include`/`exclude` parameters that respect the `response_model` field constraints. The v2 migration changed how model field serialization works. | |
| --- | |
| ## π Step 2: Relevant Files | |
| ### π Relevant Files (Ranked by Suspicion) | |
| **1. `fastapi/_compat.py`** | |
| - **Relevance score:** 10/10 | |
| - **Why relevant:** This is the Pydantic v1/v2 compatibility shim. All serialization changes went through here during the v2 migration. | |
| - **What to look for:** `_get_value()`, `serialize_response()`, any calls to `model_dump()` | |
| **2. `fastapi/routing.py`** | |
| - **Relevance score:** 9/10 | |
| - **Why relevant:** Contains `serialize_response()` calls that apply `response_model` filtering. | |
| - **What to look for:** `get_request_handler()`, how `response_model_include` and `response_model_exclude` are passed. | |
| **3. `fastapi/encoders.py`** | |
| - **Relevance score:** 7/10 | |
| - **Why relevant:** `jsonable_encoder()` handles the final conversion to JSON-safe types. | |
| - **What to look for:** Whether `include`/`exclude` sets are respected for Pydantic v2 models. | |
| --- | |
| ## π¬ Step 3: Root Cause Analysis | |
| ### Executive Summary | |
| In `fastapi/_compat.py`, the `_get_value()` function for Pydantic v2 models calls `model_dump()` without passing the `include` parameter derived from the `response_model`'s field set, causing all fields to be serialized instead of only those defined in the response model. | |
| ### π§ Chain-of-Thought Reasoning | |
| **Step 1: Entry Point** | |
| A GET request hits a route decorated with `@app.get("/users/{id}", response_model=UserOut)`. FastAPI's `routing.py:get_request_handler()` is invoked, which calls `serialize_response()`. | |
| **Step 2: Execution Trace** | |
| - `routing.py:serialize_response()` β calls `_compat.py:serialize_response()` with `response_model=UserOut` | |
| - `_compat.py:serialize_response()` calls `_get_value(response, field=response_model_field, ...)` | |
| - **Here's the bug:** For Pydantic v2, `_get_value()` calls `value.model_dump()` but does NOT pass `include=field_set` where `field_set` contains only the fields defined in `UserOut` | |
| **Step 3: The Bug** | |
| In `fastapi/_compat.py`, around line 215, the v2 branch of `_get_value()`: | |
| ```python | |
| # BUGGY (current): | |
| return value.model_dump(exclude_unset=exclude_unset, by_alias=by_alias) | |
| # Should be: | |
| return value.model_dump( | |
| include=include, | |
| exclude=exclude, | |
| exclude_unset=exclude_unset, | |
| by_alias=by_alias, | |
| ) | |
| ``` | |
| The `include` parameter (containing the `response_model`'s allowed fields) is accepted as a function argument but silently dropped in the v2 code path. | |
| **Step 4: Why This Causes the Reported Behavior** | |
| Without the `include` parameter, `model_dump()` serializes ALL fields of the returned object, bypassing the `response_model` restriction. In Pydantic v1, `_get_value()` used `dict()` which was correctly called with `include` β this broke during the v2 migration. | |
| ### π Bug Location | |
| - **File:** `fastapi/_compat.py` | |
| - **Function/Class:** `_get_value()` | |
| - **Line(s):** ~215 | |
| - **Type:** Missing parameter pass-through (regression from Pydantic v2 migration) | |
| ### π― Confidence Level | |
| **High** β The bug is clearly a missing parameter in a well-understood code path. The fix is straightforward and surgical. | |
| --- | |
| ## π§ Step 4: Proposed Fix | |
| ```diff | |
| --- a/fastapi/_compat.py | |
| +++ b/fastapi/_compat.py | |
| @@ -212,7 +212,11 @@ def _get_value( | |
| if PYDANTIC_V2: | |
| if isinstance(value, BaseModel): | |
| - return value.model_dump(exclude_unset=exclude_unset, by_alias=by_alias) | |
| + # Pass include/exclude to respect response_model field constraints | |
| + # This was missing after the Pydantic v2 migration (regression fix) | |
| + return value.model_dump( | |
| + include=include, | |
| + exclude=exclude, | |
| + exclude_unset=exclude_unset, | |
| + by_alias=by_alias, | |
| + ) | |
| ``` | |
| --- | |
| ## π Step 5: PR Description | |
| ### π Pull Request: Fix response_model field filtering with Pydantic v2 | |
| #### π Problem | |
| When using FastAPI with Pydantic v2, the `response_model` parameter on route decorators no longer strips extra fields from responses. A route returning a `UserDB` object (with password, internal fields) but declaring `response_model=UserOut` would incorrectly expose the extra fields to clients. | |
| #### π Root Cause | |
| During the Pydantic v2 migration, `fastapi/_compat.py`'s `_get_value()` function lost the `include` parameter pass-through in the v2 code path. The `model_dump()` call was not forwarding the field inclusion constraints derived from the `response_model`. | |
| #### π§ Solution | |
| Added `include=include` and `exclude=exclude` parameters to the `model_dump()` call in the Pydantic v2 branch of `_get_value()`. This restores the Pydantic v1 behavior where only `response_model` fields are serialized. | |
| #### π§ͺ Testing Recommendations | |
| 1. Create a route returning an object with extra fields, verify response only includes `response_model` fields | |
| 2. Test `response_model_exclude_unset=True` still works correctly | |
| 3. Run existing test suite: `pytest tests/test_response_model.py -v` | |
| #### β οΈ Potential Side Effects | |
| None identified. Change only affects the Pydantic v2 code path and is additive β it passes parameters that were already being constructed but not forwarded. | |
| --- | |
| *Generated by FixFlow β Autonomous Bug Resolution Agent powered by GLM 5.1 (Z.ai)* | |