-from typing import Set
+from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
description: str = None
price: float
tax: float = None
- tags: Set[str] = []
+ tags: List[str] = []
@app.post("/items/", response_model=Item)
-async def create_item(*, item: Item):
+async def create_item(item: Item):
return item
--- /dev/null
+from typing import List
+
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str = None
+ price: float
+ tax: float = 10.5
+ tags: List[str] = []
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
+}
+
+
+@app.get("/items/{item_id}", response_model=Item, response_model_skip_defaults=True)
+def read_item(item_id: str):
+ return items[item_id]
+
+
+@app.patch("/items/{item_id}", response_model=Item, response_model_skip_defaults=True)
+async def update_item(item_id: str, item: Item):
+ stored_item_data = items[item_id]
+ stored_item_model = Item(**stored_item_data)
+ update_data = item.dict(skip_defaults=True)
+ updated_item = stored_item_model.copy(update=update_data)
+ items[item_id] = updated_item
+ return updated_item
<img src="/img/tutorial/response-model/image02.png">
+## Response Model encoding parameters
+
+If your response model has default values, like:
+
+```Python hl_lines="11 13 14"
+{!./src/response_model/tutorial004.py!}
+```
+
+* `description: str = None` has a default of `None`.
+* `tax: float = None` has a default of `None`.
+* `tags: List[str] = []` has a default of an empty list: `[]`.
+
+You can set the *path operation decorator* parameter `response_model_skip_defaults=True`:
+
+```Python hl_lines="24"
+{!./src/response_model/tutorial004.py!}
+```
+
+and those default values won't be included in the response.
+
+So, if you send a request to that *path operation* for the item with ID `foo`, the response (not including default values) will be:
+
+```JSON
+{
+ "name": "Foo",
+ "price": 50.2
+}
+```
+
+!!! info
+ FastAPI uses Pydantic model's `.dict()` with <a href="https://pydantic-docs.helpmanual.io/#copying" target="_blank">its `skip_defaults` parameter</a> to achieve this.
+
+### Data with values for fields with defaults
+
+But if your data has values for the model's fields with default values, like the item with ID `bar`:
+
+```Python hl_lines="3 5"
+{
+ "name": "Bar",
+ "description": "The bartenders",
+ "price": 62,
+ "tax": 20.2
+}
+```
+
+they will be included in the response.
+
+### Data with the same values as the defaults
+
+If the data has the same values as the default ones, like the item with ID `baz`:
+
+```Python hl_lines="3 5 6"
+{
+ "name": "Baz",
+ "description": None,
+ "price": 50.2,
+ "tax": 10.5,
+ "tags": []
+}
+```
+
+FastAPI is smart enough (actually, Pydantic is smart enough) to realize that, even though `description`, `tax`, and `tags` have the same values as the defaults, they were set explicitly (instead of taken from the defaults).
+
+So, they will be included in the JSON response.
+
+!!! tip
+ Notice that the default values can be anything, not only `None`.
+
+ They can be a list (`[]`), a `float` of `10.5`, etc.
+
+### Use cases
+
+This is very useful in several scenarios.
+
+For example if you have models with many optional attributes in a NoSQL database, but you don't want to send very long JSON responses full of default values.
+
+### Using Pydantic's `skip_defaults` directly
+
+You can also use your model's `.dict(skip_defaults=True)` in your code.
+
+For example, you could receive a model object as a body payload, and update your stored data using only the attributes set, not the default ones:
+
+```Python hl_lines="31 32 33 34 35"
+{!./src/response_model/tutorial004.py!}
+```
+
+!!! tip
+ It's common to use the HTTP `PUT` operation to update data.
+
+ In theory, `PUT` should be used to "replace" the entire contents.
+
+ The less known HTTP `PATCH` operation is also used to update data.
+
+ But `PATCH` is expected to be used when *partially* updating data. Instead of *replacing* the entire content.
+
+ Still, this is just a small detail, and many teams and code bases use `PUT` instead of `PATCH` for all updates, including to *partially* update contents.
+
+ You can use `PUT` or `PATCH` however you wish.
+
## Recap
Use the path operation decorator's parameter `response_model` to define response models and especially to ensure private data is filtered out.
+
+Use `response_model_skip_defaults` to return only the values explicitly set.
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
include: Set[str] = None,
exclude: Set[str] = set(),
by_alias: bool = True,
+ skip_defaults: bool = False,
include_none: bool = True,
custom_encoder: dict = {},
sqlalchemy_safe: bool = True,
if isinstance(obj, BaseModel):
encoder = getattr(obj.Config, "json_encoders", custom_encoder)
return jsonable_encoder(
- obj.dict(include=include, exclude=exclude, by_alias=by_alias),
+ obj.dict(
+ include=include,
+ exclude=exclude,
+ by_alias=by_alias,
+ skip_defaults=skip_defaults,
+ ),
include_none=include_none,
custom_encoder=encoder,
sqlalchemy_safe=sqlalchemy_safe,
encoded_key = jsonable_encoder(
key,
by_alias=by_alias,
+ skip_defaults=skip_defaults,
include_none=include_none,
custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe,
encoded_value = jsonable_encoder(
value,
by_alias=by_alias,
+ skip_defaults=skip_defaults,
include_none=include_none,
custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe,
include=include,
exclude=exclude,
by_alias=by_alias,
+ skip_defaults=skip_defaults,
include_none=include_none,
custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe,
return jsonable_encoder(
data,
by_alias=by_alias,
+ skip_defaults=skip_defaults,
include_none=include_none,
custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe,
from starlette.websockets import WebSocket
-def serialize_response(*, field: Field = None, response: Response) -> Any:
- encoded = jsonable_encoder(response)
+def serialize_response(
+ *, field: Field = None, response: Response, skip_defaults: bool = False
+) -> Any:
+
+ encoded = jsonable_encoder(response, skip_defaults=skip_defaults)
if field:
errors = []
value, errors_ = field.validate(encoded, {}, loc=("response",))
errors.extend(errors_)
if errors:
raise ValidationError(errors)
- return jsonable_encoder(value)
+ return jsonable_encoder(value, skip_defaults=skip_defaults)
else:
return encoded
status_code: int = 200,
response_class: Type[Response] = JSONResponse,
response_field: Field = None,
+ skip_defaults: bool = False,
) -> Callable:
assert dependant.call is not None, "dependant.call must be a function"
is_coroutine = asyncio.iscoroutinefunction(dependant.call)
raw_response.background = background_tasks
return raw_response
response_data = serialize_response(
- field=response_field, response=raw_response
+ field=response_field, response=raw_response, skip_defaults=skip_defaults
)
return response_class(
content=response_data,
name: str = None,
methods: List[str] = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
) -> None:
methods = ["GET"]
self.methods = methods
self.operation_id = operation_id
+ self.response_model_skip_defaults = response_model_skip_defaults
self.include_in_schema = include_in_schema
self.response_class = response_class
status_code=self.status_code,
response_class=self.response_class,
response_field=self.response_field,
+ skip_defaults=self.response_model_skip_defaults,
)
)
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
deprecated=route.deprecated,
methods=route.methods,
operation_id=route.operation_id,
+ response_model_skip_defaults=route.response_model_skip_defaults,
include_in_schema=route.include_in_schema,
response_class=route.response_class,
name=route.name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
) -> Callable:
+
return self.api_route(
path=path,
response_model=response_model,
deprecated=deprecated,
methods=["GET"],
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
deprecated=deprecated,
methods=["PUT"],
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
deprecated=deprecated,
methods=["POST"],
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
deprecated=deprecated,
methods=["DELETE"],
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
deprecated=deprecated,
methods=["OPTIONS"],
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
deprecated=deprecated,
methods=["HEAD"],
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
deprecated=deprecated,
methods=["PATCH"],
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
name: str = None,
deprecated=deprecated,
methods=["TRACE"],
operation_id=operation_id,
+ response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
--- /dev/null
+import pytest
+from starlette.testclient import TestClient
+
+from response_model.tutorial004 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "Fast API", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Item",
+ "operationId": "read_item_items__item_id__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item_Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ },
+ "patch": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item",
+ "operationId": "update_item_items__item_id__patch",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item_Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ },
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"title": "Tax", "type": "number", "default": 10.5},
+ "tags": {
+ "title": "Tags",
+ "type": "array",
+ "items": {"type": "string"},
+ "default": [],
+ },
+ },
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": ["loc", "msg", "type"],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {"type": "string"},
+ },
+ "msg": {"title": "Message", "type": "string"},
+ "type": {"title": "Error Type", "type": "string"},
+ },
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/ValidationError"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+def test_openapi_schema():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == openapi_schema
+
+
+@pytest.mark.parametrize(
+ "url,data",
+ [
+ ("/items/foo", {"name": "Foo", "price": 50.2}),
+ (
+ "/items/bar",
+ {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
+ ),
+ (
+ "/items/baz",
+ {
+ "name": "Baz",
+ "description": None,
+ "price": 50.2,
+ "tax": 10.5,
+ "tags": [],
+ },
+ ),
+ ],
+)
+def test_get(url, data):
+ response = client.get(url)
+ assert response.status_code == 200
+ assert response.json() == data
+
+
+def test_patch():
+ response = client.patch(
+ "/items/bar", json={"name": "Barz", "price": 3, "description": None}
+ )
+ assert response.json() == {
+ "name": "Barz",
+ "description": None,
+ "price": 3,
+ "tax": 20.2,
+ }