This way you can add correct type annotations to your functions even when you are returning a type different than the response model, to be used by the editor and tools like mypy. And still you can have FastAPI do the data validation, documentation, etc. using the `response_model`.
+You can also use `response_model=None` to disable creating a response model for that *path operation*, you might need to do it if you are adding type annotations for things that are not valid Pydantic fields, you will see an example of that in one of the sections below.
+
## Return the same input data
Here we are declaring a `UserIn` model, it will contain a plaintext password:
<img src="/img/tutorial/response-model/image02.png">
+## Other Return Type Annotations
+
+There might be cases where you return something that is not a valid Pydantic field and you annotate it in the function, only to get the support provided by tooling (the editor, mypy, etc).
+
+### Return a Response Directly
+
+The most common case would be [returning a Response directly as explained later in the advanced docs](../advanced/response-directly.md){.internal-link target=_blank}.
+
+```Python hl_lines="8 10-11"
+{!> ../../../docs_src/response_model/tutorial003_02.py!}
+```
+
+This simple case is handled automatically by FastAPI because the return type annotation is the class (or a subclass) of `Response`.
+
+And tools will also be happy because both `RedirectResponse` and `JSONResponse` are subclasses of `Response`, so the type annotation is correct.
+
+### Annotate a Response Subclass
+
+You can also use a subclass of `Response` in the type annotation:
+
+```Python hl_lines="8-9"
+{!> ../../../docs_src/response_model/tutorial003_03.py!}
+```
+
+This will also work because `RedirectResponse` is a subclass of `Response`, and FastAPI will automatically handle this simple case.
+
+### Invalid Return Type Annotations
+
+But when you return some other arbitrary object that is not a valid Pydantic type (e.g. a database object) and you annotate it like that in the function, FastAPI will try to create a Pydantic response model from that type annotation, and will fail.
+
+The same would happen if you had something like a <abbr title='A union between multiple types means "any of these types".'>union</abbr> between different types where one or more of them are not valid Pydantic types, for example this would fail 💥:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="10"
+ {!> ../../../docs_src/response_model/tutorial003_04.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="8"
+ {!> ../../../docs_src/response_model/tutorial003_04_py310.py!}
+ ```
+
+...this fails because the type annotation is not a Pydantic type and is not just a single `Response` class or subclass, it's a union (any of the two) between a `Response` and a `dict`.
+
+### Disable Response Model
+
+Continuing from the example above, you might not want to have the default data validation, documentation, filtering, etc. that is performed by FastAPI.
+
+But you might want to still keep the return type annotation in the function to get the support from tools like editors and type checkers (e.g. mypy).
+
+In this case, you can disable the response model generation by setting `response_model=None`:
+
+=== "Python 3.6 and above"
+
+ ```Python hl_lines="9"
+ {!> ../../../docs_src/response_model/tutorial003_05.py!}
+ ```
+
+=== "Python 3.10 and above"
+
+ ```Python hl_lines="7"
+ {!> ../../../docs_src/response_model/tutorial003_05_py310.py!}
+ ```
+
+This will make FastAPI skip the response model generation and that way you can have any return type annotations you need without it affecting your FastAPI application. 🤓
+
## Response Model encoding parameters
Your response model could have default values, like:
--- /dev/null
+from fastapi import FastAPI, Response
+from fastapi.responses import JSONResponse, RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal")
+async def get_portal(teleport: bool = False) -> Response:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return JSONResponse(content={"message": "Here's your interdimensional portal."})
--- /dev/null
+from fastapi import FastAPI
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/teleport")
+async def get_teleport() -> RedirectResponse:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
--- /dev/null
+from typing import Union
+
+from fastapi import FastAPI, Response
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal")
+async def get_portal(teleport: bool = False) -> Union[Response, dict]:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return {"message": "Here's your interdimensional portal."}
--- /dev/null
+from fastapi import FastAPI, Response
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal")
+async def get_portal(teleport: bool = False) -> Response | dict:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return {"message": "Here's your interdimensional portal."}
--- /dev/null
+from typing import Union
+
+from fastapi import FastAPI, Response
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal", response_model=None)
+async def get_portal(teleport: bool = False) -> Union[Response, dict]:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return {"message": "Here's your interdimensional portal."}
--- /dev/null
+from fastapi import FastAPI, Response
+from fastapi.responses import RedirectResponse
+
+app = FastAPI()
+
+
+@app.get("/portal", response_model=None)
+async def get_portal(teleport: bool = False) -> Response | dict:
+ if teleport:
+ return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
+ return {"message": "Here's your interdimensional portal."}
return response_field(field_info=field_info)
except RuntimeError:
raise fastapi.exceptions.FastAPIError(
- f"Invalid args for response field! Hint: check that {type_} is a valid pydantic field type"
+ "Invalid args for response field! Hint: "
+ f"check that {type_} is a valid Pydantic field type. "
+ "If you are using a return type annotation that is not a valid Pydantic "
+ "field (e.g. Union[Response, dict, None]) you can disable generating the "
+ "response model from the type annotation with the path operation decorator "
+ "parameter response_model=None. Read more: "
+ "https://fastapi.tiangolo.com/tutorial/response-model/"
) from None
"fastapi"
]
context = '${CONTEXT}'
+omit = [
+ "docs_src/response_model/tutorial003_04.py",
+ "docs_src/response_model/tutorial003_04_py310.py",
+]
[tool.ruff]
select = [
import pytest
from fastapi import FastAPI
+from fastapi.exceptions import FastAPIError
from fastapi.responses import JSONResponse, Response
from fastapi.testclient import TestClient
from pydantic import BaseModel, ValidationError
response = client.get("/no_response_model-annotation_json_response_class")
assert response.status_code == 200, response.text
assert response.json() == {"foo": "bar"}
+
+
+def test_invalid_response_model_field():
+ app = FastAPI()
+ with pytest.raises(FastAPIError) as e:
+
+ @app.get("/")
+ def read_root() -> Union[Response, None]:
+ return Response(content="Foo") # pragma: no cover
+
+ assert "valid Pydantic field type" in e.value.args[0]
+ assert "parameter response_model=None" in e.value.args[0]
--- /dev/null
+from fastapi.testclient import TestClient
+
+from docs_src.response_model.tutorial003_01 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/user/": {
+ "post": {
+ "summary": "Create User",
+ "operationId": "create_user_user__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/UserIn"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/BaseUser"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "BaseUser": {
+ "title": "BaseUser",
+ "required": ["username", "email"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {"title": "Email", "type": "string", "format": "email"},
+ "full_name": {"title": "Full Name", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "UserIn": {
+ "title": "UserIn",
+ "required": ["username", "email", "password"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {"title": "Email", "type": "string", "format": "email"},
+ "full_name": {"title": "Full Name", "type": "string"},
+ "password": {"title": "Password", "type": "string"},
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_post_user():
+ response = client.post(
+ "/user/",
+ json={
+ "username": "foo",
+ "password": "fighter",
+ "email": "foo@example.com",
+ "full_name": "Grave Dohl",
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "username": "foo",
+ "email": "foo@example.com",
+ "full_name": "Grave Dohl",
+ }
--- /dev/null
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/user/": {
+ "post": {
+ "summary": "Create User",
+ "operationId": "create_user_user__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/UserIn"}
+ }
+ },
+ "required": True,
+ },
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/BaseUser"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "BaseUser": {
+ "title": "BaseUser",
+ "required": ["username", "email"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {"title": "Email", "type": "string", "format": "email"},
+ "full_name": {"title": "Full Name", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "UserIn": {
+ "title": "UserIn",
+ "required": ["username", "email", "password"],
+ "type": "object",
+ "properties": {
+ "username": {"title": "Username", "type": "string"},
+ "email": {"title": "Email", "type": "string", "format": "email"},
+ "full_name": {"title": "Full Name", "type": "string"},
+ "password": {"title": "Password", "type": "string"},
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.response_model.tutorial003_01_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_post_user(client: TestClient):
+ response = client.post(
+ "/user/",
+ json={
+ "username": "foo",
+ "password": "fighter",
+ "email": "foo@example.com",
+ "full_name": "Grave Dohl",
+ },
+ )
+ assert response.status_code == 200, response.text
+ assert response.json() == {
+ "username": "foo",
+ "email": "foo@example.com",
+ "full_name": "Grave Dohl",
+ }
--- /dev/null
+from fastapi.testclient import TestClient
+
+from docs_src.response_model.tutorial003_02 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/portal": {
+ "get": {
+ "summary": "Get Portal",
+ "operationId": "get_portal_portal_get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Teleport",
+ "type": "boolean",
+ "default": False,
+ },
+ "name": "teleport",
+ "in": "query",
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_get_portal():
+ response = client.get("/portal")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Here's your interdimensional portal."}
+
+
+def test_get_redirect():
+ response = client.get("/portal", params={"teleport": True}, follow_redirects=False)
+ assert response.status_code == 307, response.text
+ assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
--- /dev/null
+from fastapi.testclient import TestClient
+
+from docs_src.response_model.tutorial003_03 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/teleport": {
+ "get": {
+ "summary": "Get Teleport",
+ "operationId": "get_teleport_teleport_get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ }
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_get_portal():
+ response = client.get("/teleport", follow_redirects=False)
+ assert response.status_code == 307, response.text
+ assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
--- /dev/null
+import pytest
+from fastapi.exceptions import FastAPIError
+
+
+def test_invalid_response_model():
+ with pytest.raises(FastAPIError):
+ from docs_src.response_model.tutorial003_04 import app
+
+ assert app # pragma: no cover
--- /dev/null
+import pytest
+from fastapi.exceptions import FastAPIError
+
+from ...utils import needs_py310
+
+
+@needs_py310
+def test_invalid_response_model():
+ with pytest.raises(FastAPIError):
+ from docs_src.response_model.tutorial003_04_py310 import app
+
+ assert app # pragma: no cover
--- /dev/null
+from fastapi.testclient import TestClient
+
+from docs_src.response_model.tutorial003_05 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/portal": {
+ "get": {
+ "summary": "Get Portal",
+ "operationId": "get_portal_portal_get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Teleport",
+ "type": "boolean",
+ "default": False,
+ },
+ "name": "teleport",
+ "in": "query",
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+def test_get_portal():
+ response = client.get("/portal")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Here's your interdimensional portal."}
+
+
+def test_get_redirect():
+ response = client.get("/portal", params={"teleport": True}, follow_redirects=False)
+ assert response.status_code == 307, response.text
+ assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
--- /dev/null
+import pytest
+from fastapi.testclient import TestClient
+
+from ...utils import needs_py310
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/portal": {
+ "get": {
+ "summary": "Get Portal",
+ "operationId": "get_portal_portal_get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {
+ "title": "Teleport",
+ "type": "boolean",
+ "default": False,
+ },
+ "name": "teleport",
+ "in": "query",
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from docs_src.response_model.tutorial003_05_py310 import app
+
+ client = TestClient(app)
+ return client
+
+
+@needs_py310
+def test_openapi_schema(client: TestClient):
+ response = client.get("/openapi.json")
+ assert response.status_code == 200, response.text
+ assert response.json() == openapi_schema
+
+
+@needs_py310
+def test_get_portal(client: TestClient):
+ response = client.get("/portal")
+ assert response.status_code == 200, response.text
+ assert response.json() == {"message": "Here's your interdimensional portal."}
+
+
+@needs_py310
+def test_get_redirect(client: TestClient):
+ response = client.get("/portal", params={"teleport": True}, follow_redirects=False)
+ assert response.status_code == 307, response.text
+ assert response.headers["location"] == "https://www.youtube.com/watch?v=dQw4w9WgXcQ"