diff --git a/docs/src/response_model/tutorial001.py b/docs/src/response_model/tutorial001.py
index 86dadcbda..4fe9aeb50 100644
--- a/docs/src/response_model/tutorial001.py
+++ b/docs/src/response_model/tutorial001.py
@@ -1,4 +1,4 @@
-from typing import Set
+from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
@@ -11,9 +11,9 @@ class Item(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
diff --git a/docs/src/response_model/tutorial004.py b/docs/src/response_model/tutorial004.py
new file mode 100644
index 000000000..30ad2184b
--- /dev/null
+++ b/docs/src/response_model/tutorial004.py
@@ -0,0 +1,36 @@
+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
diff --git a/docs/tutorial/response-model.md b/docs/tutorial/response-model.md
index 21b885ebb..1402a6a8f 100644
--- a/docs/tutorial/response-model.md
+++ b/docs/tutorial/response-model.md
@@ -82,6 +82,107 @@ And both models will be used for the interactive API documentation:
+## 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 its `skip_defaults` parameter 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.
diff --git a/fastapi/applications.py b/fastapi/applications.py
index 7041e91d6..3d6071ae6 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -138,6 +138,7 @@ class FastAPI(Starlette):
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,
@@ -156,6 +157,7 @@ class FastAPI(Starlette):
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,
@@ -176,6 +178,7 @@ class FastAPI(Starlette):
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,
@@ -195,6 +198,7 @@ class FastAPI(Starlette):
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,
@@ -246,6 +250,7 @@ class FastAPI(Starlette):
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,
@@ -262,6 +267,7 @@ class FastAPI(Starlette):
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,
@@ -281,6 +287,7 @@ class FastAPI(Starlette):
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,
@@ -297,6 +304,7 @@ class FastAPI(Starlette):
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,
@@ -316,6 +324,7 @@ class FastAPI(Starlette):
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,
@@ -332,6 +341,7 @@ class FastAPI(Starlette):
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,
@@ -351,6 +361,7 @@ class FastAPI(Starlette):
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,
@@ -367,6 +378,7 @@ class FastAPI(Starlette):
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,
@@ -386,6 +398,7 @@ class FastAPI(Starlette):
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,
@@ -402,6 +415,7 @@ class FastAPI(Starlette):
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,
@@ -421,6 +435,7 @@ class FastAPI(Starlette):
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,
@@ -437,6 +452,7 @@ class FastAPI(Starlette):
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,
@@ -456,6 +472,7 @@ class FastAPI(Starlette):
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,
@@ -472,6 +489,7 @@ class FastAPI(Starlette):
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,
@@ -491,6 +509,7 @@ class FastAPI(Starlette):
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,
@@ -507,6 +526,7 @@ class FastAPI(Starlette):
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,
diff --git a/fastapi/encoders.py b/fastapi/encoders.py
index fc289c936..25ab19fa3 100644
--- a/fastapi/encoders.py
+++ b/fastapi/encoders.py
@@ -11,6 +11,7 @@ def jsonable_encoder(
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,
@@ -18,7 +19,12 @@ def jsonable_encoder(
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,
@@ -42,6 +48,7 @@ def jsonable_encoder(
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,
@@ -49,6 +56,7 @@ def jsonable_encoder(
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,
@@ -64,6 +72,7 @@ def jsonable_encoder(
include=include,
exclude=exclude,
by_alias=by_alias,
+ skip_defaults=skip_defaults,
include_none=include_none,
custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe,
@@ -91,6 +100,7 @@ def jsonable_encoder(
return jsonable_encoder(
data,
by_alias=by_alias,
+ skip_defaults=skip_defaults,
include_none=include_none,
custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe,
diff --git a/fastapi/routing.py b/fastapi/routing.py
index c902bb2ad..2ae6d1e09 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -32,8 +32,11 @@ from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY, WS_1008_POLICY_VIOLA
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",))
@@ -43,7 +46,7 @@ def serialize_response(*, field: Field = None, response: Response) -> Any:
errors.extend(errors_)
if errors:
raise ValidationError(errors)
- return jsonable_encoder(value)
+ return jsonable_encoder(value, skip_defaults=skip_defaults)
else:
return encoded
@@ -54,6 +57,7 @@ def get_app(
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)
@@ -93,7 +97,7 @@ def get_app(
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,
@@ -151,6 +155,7 @@ class APIRoute(routing.Route):
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:
@@ -210,6 +215,7 @@ class APIRoute(routing.Route):
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
@@ -230,6 +236,7 @@ class APIRoute(routing.Route):
status_code=self.status_code,
response_class=self.response_class,
response_field=self.response_field,
+ skip_defaults=self.response_model_skip_defaults,
)
)
@@ -251,6 +258,7 @@ class APIRouter(routing.Router):
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,
@@ -269,6 +277,7 @@ class APIRouter(routing.Router):
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,
@@ -290,6 +299,7 @@ class APIRouter(routing.Router):
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,
@@ -309,6 +319,7 @@ class APIRouter(routing.Router):
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,
@@ -363,6 +374,7 @@ class APIRouter(routing.Router):
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,
@@ -398,10 +410,12 @@ class APIRouter(routing.Router):
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,
@@ -415,6 +429,7 @@ class APIRouter(routing.Router):
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,
@@ -434,6 +449,7 @@ class APIRouter(routing.Router):
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,
@@ -451,6 +467,7 @@ class APIRouter(routing.Router):
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,
@@ -470,6 +487,7 @@ class APIRouter(routing.Router):
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,
@@ -487,6 +505,7 @@ class APIRouter(routing.Router):
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,
@@ -506,6 +525,7 @@ class APIRouter(routing.Router):
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,
@@ -523,6 +543,7 @@ class APIRouter(routing.Router):
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,
@@ -542,6 +563,7 @@ class APIRouter(routing.Router):
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,
@@ -559,6 +581,7 @@ class APIRouter(routing.Router):
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,
@@ -578,6 +601,7 @@ class APIRouter(routing.Router):
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,
@@ -595,6 +619,7 @@ class APIRouter(routing.Router):
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,
@@ -614,6 +639,7 @@ class APIRouter(routing.Router):
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,
@@ -631,6 +657,7 @@ class APIRouter(routing.Router):
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,
@@ -650,6 +677,7 @@ class APIRouter(routing.Router):
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,
@@ -667,6 +695,7 @@ class APIRouter(routing.Router):
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,
diff --git a/tests/test_tutorial/test_response_model/test_tutorial004.py b/tests/test_tutorial/test_response_model/test_tutorial004.py
new file mode 100644
index 000000000..152e291e4
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial004.py
@@ -0,0 +1,177 @@
+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,
+ }