!!! note
Notice that you have to return the image using a `FileResponse` directly.
+!!! info
+ Unless you specify a different media type explicitly in your `responses` parameter, FastAPI will assume the response has the same media type as the main response class (default `application/json`).
+
+ But if you have specified a custom response class with `None` as its media type, FastAPI will use `application/json` for any additional response that has an associated model.
+
## Combining information
You can also combine response information from multiple places, including the `response_model`, `status_code`, and `responses` parameters.
And if that `Response` has a JSON media type (`application/json`), like is the case with the `JSONResponse` and `UJSONResponse`, the data you return will be automatically converted (and filtered) with any Pydantic `response_model` that you declared in the *path operation decorator*.
+!!! note
+ If you use a response class with no media type, FastAPI will expect your response to have no content, so it will not document the response format in its generated OpenAPI docs.
+
## Use `UJSONResponse`
For example, if you are squeezing performance, you can install and use `ujson` and set the response to be Starlette's `UJSONResponse`.
<img src="/img/tutorial/response-status-code/image01.png">
+!!! note
+ Some response codes (see the next section) indicate that the response does not have a body.
+
+ FastAPI knows this, and will produce OpenAPI docs that state there is no response body.
## About HTTP status codes
In short:
-* `100` and above are for "Information". You rarely use them directly.
+* `100` and above are for "Information". You rarely use them directly. Responses with these status codes cannot have a body.
* **`200`** and above are for "Successful" responses. These are the ones you would use the most.
* `200` is the default status code, which means everything was "OK".
* Another example would be `201`, "Created". It is commonly used after creating a new record in the database.
-* `300` and above are for "Redirection".
+ * A special case is `204`, "No Content". This response is used when there is no content to return to the client, and so the response must not have a body.
+* **`300`** and above are for "Redirection". Responses with these status codes may or may not have a body, except for `304`, "Not Modified", which must not have one.
* **`400`** and above are for "Client error" responses. These are the second type you would probably use the most.
* An example is `404`, for a "Not Found" response.
* For generic errors from the client, you can just use `400`.
METHODS_WITH_BODY = set(("POST", "PUT", "DELETE", "PATCH"))
+STATUS_CODES_WITH_NO_BODY = set((100, 101, 102, 103, 204, 304))
REF_PREFIX = "#/components/schemas/"
from fastapi.dependencies.models import Dependant
from fastapi.dependencies.utils import get_flat_dependant
from fastapi.encoders import jsonable_encoder
-from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX
+from fastapi.openapi.constants import (
+ METHODS_WITH_BODY,
+ REF_PREFIX,
+ STATUS_CODES_WITH_NO_BODY,
+)
from fastapi.openapi.models import OpenAPI
from fastapi.params import Body, Param
from fastapi.utils import (
security_schemes: Dict[str, Any] = {}
definitions: Dict[str, Any] = {}
assert route.methods is not None, "Methods must be a list"
- assert (
- route.response_class and route.response_class.media_type
- ), "A response class with media_type is needed to generate OpenAPI"
- route_response_media_type: str = route.response_class.media_type
+ assert route.response_class, "A response class is needed to generate OpenAPI"
+ route_response_media_type: Optional[str] = route.response_class.media_type
if route.include_in_schema:
for method in route.methods:
operation = get_openapi_operation_metadata(route=route, method=method)
field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
)
response.setdefault("content", {}).setdefault(
- route_response_media_type, {}
+ route_response_media_type or "application/json", {}
)["schema"] = response_schema
status_text: Optional[str] = status_code_ranges.get(
str(additional_status_code).upper()
status_code_key = "default"
operation.setdefault("responses", {})[status_code_key] = response
status_code = str(route.status_code)
- response_schema = {"type": "string"}
- if lenient_issubclass(route.response_class, JSONResponse):
- if route.response_field:
- response_schema, _, _ = field_schema(
- route.response_field,
- model_name_map=model_name_map,
- ref_prefix=REF_PREFIX,
- )
- else:
- response_schema = {}
operation.setdefault("responses", {}).setdefault(status_code, {})[
"description"
] = route.response_description
- operation.setdefault("responses", {}).setdefault(
- status_code, {}
- ).setdefault("content", {}).setdefault(route_response_media_type, {})[
- "schema"
- ] = response_schema
+ if (
+ route_response_media_type
+ and route.status_code not in STATUS_CODES_WITH_NO_BODY
+ ):
+ response_schema = {"type": "string"}
+ if lenient_issubclass(route.response_class, JSONResponse):
+ if route.response_field:
+ response_schema, _, _ = field_schema(
+ route.response_field,
+ model_name_map=model_name_map,
+ ref_prefix=REF_PREFIX,
+ )
+ else:
+ response_schema = {}
+ operation.setdefault("responses", {}).setdefault(
+ status_code, {}
+ ).setdefault("content", {}).setdefault(route_response_media_type, {})[
+ "schema"
+ ] = response_schema
http422 = str(HTTP_422_UNPROCESSABLE_ENTITY)
if (all_route_params or route.body_field) and not any(
)
from fastapi.encoders import DictIntStrAny, SetIntStr, jsonable_encoder
from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
+from fastapi.openapi.constants import STATUS_CODES_WITH_NO_BODY
from fastapi.utils import create_cloned_field, generate_operation_id_for_path
from pydantic import BaseConfig, BaseModel, Schema
from pydantic.error_wrappers import ErrorWrapper, ValidationError
)
self.response_model = response_model
if self.response_model:
+ assert (
+ status_code not in STATUS_CODES_WITH_NO_BODY
+ ), f"Status code {status_code} must not have a response body"
response_name = "Response_" + self.unique_id
self.response_field: Optional[Field] = Field(
name=response_name,
assert isinstance(response, dict), "An additional response must be a dict"
model = response.get("model")
if model:
+ assert (
+ additional_status_code not in STATUS_CODES_WITH_NO_BODY
+ ), f"Status code {additional_status_code} must not have a response body"
assert lenient_issubclass(
model, BaseModel
), "A response model must be a Pydantic model"
--- /dev/null
+import typing
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+from starlette.responses import JSONResponse, Response
+from starlette.testclient import TestClient
+
+app = FastAPI()
+
+
+class JsonApiResponse(JSONResponse):
+ media_type = "application/vnd.api+json"
+
+
+class Error(BaseModel):
+ status: str
+ title: str
+
+
+class JsonApiError(BaseModel):
+ errors: typing.List[Error]
+
+
+@app.get(
+ "/a",
+ response_class=Response,
+ responses={500: {"description": "Error", "model": JsonApiError}},
+)
+async def a():
+ pass # pragma: no cover
+
+
+@app.get("/b", responses={500: {"description": "Error", "model": Error}})
+async def b():
+ pass # pragma: no cover
+
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "Fast API", "version": "0.1.0"},
+ "paths": {
+ "/a": {
+ "get": {
+ "responses": {
+ "500": {
+ "description": "Error",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/JsonApiError"}
+ }
+ },
+ },
+ "200": {"description": "Successful Response"},
+ },
+ "summary": "A",
+ "operationId": "a_a_get",
+ }
+ },
+ "/b": {
+ "get": {
+ "responses": {
+ "500": {
+ "description": "Error",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Error"}
+ }
+ },
+ },
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ },
+ "summary": "B",
+ "operationId": "b_b_get",
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Error": {
+ "title": "Error",
+ "required": ["status", "title"],
+ "type": "object",
+ "properties": {
+ "status": {"title": "Status", "type": "string"},
+ "title": {"title": "Title", "type": "string"},
+ },
+ },
+ "JsonApiError": {
+ "title": "JsonApiError",
+ "required": ["errors"],
+ "type": "object",
+ "properties": {
+ "errors": {
+ "title": "Errors",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Error"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+client = TestClient(app)
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == openapi_schema
--- /dev/null
+import typing
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+from starlette.responses import JSONResponse
+from starlette.testclient import TestClient
+
+app = FastAPI()
+
+
+class JsonApiResponse(JSONResponse):
+ media_type = "application/vnd.api+json"
+
+
+class Error(BaseModel):
+ status: str
+ title: str
+
+
+class JsonApiError(BaseModel):
+ errors: typing.List[Error]
+
+
+@app.get(
+ "/a",
+ status_code=204,
+ response_class=JsonApiResponse,
+ responses={500: {"description": "Error", "model": JsonApiError}},
+)
+async def a():
+ pass # pragma: no cover
+
+
+@app.get("/b", responses={204: {"description": "No Content"}})
+async def b():
+ pass # pragma: no cover
+
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "Fast API", "version": "0.1.0"},
+ "paths": {
+ "/a": {
+ "get": {
+ "responses": {
+ "500": {
+ "description": "Error",
+ "content": {
+ "application/vnd.api+json": {
+ "schema": {"$ref": "#/components/schemas/JsonApiError"}
+ }
+ },
+ },
+ "204": {"description": "Successful Response"},
+ },
+ "summary": "A",
+ "operationId": "a_a_get",
+ }
+ },
+ "/b": {
+ "get": {
+ "responses": {
+ "204": {"description": "No Content"},
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ },
+ "summary": "B",
+ "operationId": "b_b_get",
+ }
+ },
+ },
+ "components": {
+ "schemas": {
+ "Error": {
+ "title": "Error",
+ "required": ["status", "title"],
+ "type": "object",
+ "properties": {
+ "status": {"title": "Status", "type": "string"},
+ "title": {"title": "Title", "type": "string"},
+ },
+ },
+ "JsonApiError": {
+ "title": "JsonApiError",
+ "required": ["errors"],
+ "type": "object",
+ "properties": {
+ "errors": {
+ "title": "Errors",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/Error"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+client = TestClient(app)
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == openapi_schema