diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 6ed3fcc55..7e50cb7c6 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -931,7 +931,7 @@ async def request_body_to_args( if ( single_not_embedded_field - and resolves_to_basemodel(first_field.type_) + and lenient_issubclass(first_field.type_, BaseModel) and isinstance(received_body, FormData) ): fields_to_extract = get_cached_model_fields(first_field.type_) diff --git a/tests/test_root_model.py b/tests/test_root_model.py index 95a18f460..85a8e65d7 100644 --- a/tests/test_root_model.py +++ b/tests/test_root_model.py @@ -280,6 +280,10 @@ def test_root_model_union(left: Any, right: Any): def handler2(b: Annotated[Model, Body()]): return {"b": b} + @app2.post("/body2") + def handler2(q: Model) -> Model: + return q + client2 = TestClient(app2) for val in ["2025-01-02", 42]: response = client2.get(f"/query?q={val}") @@ -289,6 +293,15 @@ def test_root_model_union(left: Any, right: Any): assert response.status_code == 200, response.text assert response.json() == {"b": val} + # By default, RootModel is regarded as a body parameter + response = ( + client2.post("/body2", json=val) + if issubclass(left, RootModel) or issubclass(right, RootModel) + else client2.post(f"/body2?q={val}") + ) + assert response.status_code == 200, response.text + assert response.json() == val + response = client2.get("/query?q=bad") assert response.status_code == 422, response.text assert {detail["msg"] for detail in response.json()["detail"]} == {