Spaces:
Running
Running
File size: 7,161 Bytes
cd3b358 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | # 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)*
|