diff --git a/docs/src/body_updates/tutorial001.py b/docs/src/body_updates/tutorial001.py
new file mode 100644
index 000000000..bf9249327
--- /dev/null
+++ b/docs/src/body_updates/tutorial001.py
@@ -0,0 +1,34 @@
+from typing import List
+
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str = None
+ description: str = None
+ price: float = None
+ 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)
+async def read_item(item_id: str):
+ return items[item_id]
+
+
+@app.put("/items/{item_id}", response_model=Item)
+async def update_item(item_id: str, item: Item):
+ update_item_encoded = jsonable_encoder(item)
+ items[item_id] = update_item_encoded
+ return update_item_encoded
diff --git a/docs/src/body_updates/tutorial002.py b/docs/src/body_updates/tutorial002.py
new file mode 100644
index 000000000..e10fbb921
--- /dev/null
+++ b/docs/src/body_updates/tutorial002.py
@@ -0,0 +1,37 @@
+from typing import List
+
+from fastapi import FastAPI
+from fastapi.encoders import jsonable_encoder
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str = None
+ description: str = None
+ price: float = None
+ 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)
+async def read_item(item_id: str):
+ return items[item_id]
+
+
+@app.patch("/items/{item_id}", response_model=Item)
+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] = jsonable_encoder(updated_item)
+ return updated_item
diff --git a/docs/src/response_model/tutorial004.py b/docs/src/response_model/tutorial004.py
index 30ad2184b..bbfd855bb 100644
--- a/docs/src/response_model/tutorial004.py
+++ b/docs/src/response_model/tutorial004.py
@@ -22,15 +22,5 @@ items = {
@app.get("/items/{item_id}", response_model=Item, response_model_skip_defaults=True)
-def read_item(item_id: str):
+async 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/src/response_model/tutorial005.py b/docs/src/response_model/tutorial005.py
new file mode 100644
index 000000000..65343f072
--- /dev/null
+++ b/docs/src/response_model/tutorial005.py
@@ -0,0 +1,37 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str = None
+ price: float
+ tax: float = 10.5
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
+ "baz": {
+ "name": "Baz",
+ "description": "There goes my baz",
+ "price": 50.2,
+ "tax": 10.5,
+ },
+}
+
+
+@app.get(
+ "/items/{item_id}/name",
+ response_model=Item,
+ response_model_include={"name", "description"},
+)
+async def read_item_name(item_id: str):
+ return items[item_id]
+
+
+@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"})
+async def read_item_public_data(item_id: str):
+ return items[item_id]
diff --git a/docs/src/response_model/tutorial006.py b/docs/src/response_model/tutorial006.py
new file mode 100644
index 000000000..127c6f7cf
--- /dev/null
+++ b/docs/src/response_model/tutorial006.py
@@ -0,0 +1,37 @@
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+
+class Item(BaseModel):
+ name: str
+ description: str = None
+ price: float
+ tax: float = 10.5
+
+
+items = {
+ "foo": {"name": "Foo", "price": 50.2},
+ "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
+ "baz": {
+ "name": "Baz",
+ "description": "There goes my baz",
+ "price": 50.2,
+ "tax": 10.5,
+ },
+}
+
+
+@app.get(
+ "/items/{item_id}/name",
+ response_model=Item,
+ response_model_include=["name", "description"],
+)
+async def read_item_name(item_id: str):
+ return items[item_id]
+
+
+@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude=["tax"])
+async def read_item_public_data(item_id: str):
+ return items[item_id]
diff --git a/docs/tutorial/body-updates.md b/docs/tutorial/body-updates.md
new file mode 100644
index 000000000..0850d5d44
--- /dev/null
+++ b/docs/tutorial/body-updates.md
@@ -0,0 +1,97 @@
+## Update replacing with `PUT`
+
+To update an item you can use the [HTTP `PUT`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) operation.
+
+You can use the `jsonable_encoder` to convert the input data to data that can be stored as JSON (e.g. with a NoSQL database). For example, converting `datetime` to `str`.
+
+```Python hl_lines="30 31 32 33 34 35"
+{!./src/body_updates/tutorial001.py!}
+```
+
+`PUT` is used to receive data that should replace the existing data.
+
+### Warning about replacing
+
+That means that if you want to update the item `bar` using `PUT` with a body containing:
+
+```Python
+{
+ "name": "Barz",
+ "price": 3,
+ "description": None,
+}
+```
+
+because it doesn't include the already stored attribute `"tax": 20.2`, the input model would take the default value of `"tax": 10.5`.
+
+And the data would be saved with that "new" `tax` of `10.5`.
+
+## Partial updates with `PATCH`
+
+You can also use the [HTTP `PATCH`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH) operation to *partially* update data.
+
+This means that you can send only the data that you want to update, leaving the rest intact.
+
+!!! Note
+ `PATCH` is less commonly used and known than `PUT`.
+
+ And many teams use only `PUT`, even for partial updates.
+
+ You are **free** to use them however you want, **FastAPI** doesn't impose any restrictions.
+
+ But this guide shows you, more or less, how they are intended to be used.
+
+### Using Pydantic's `skip_defaults` parameter
+
+If you want to receive partial updates, it's very useful to use the parameter `skip_defaults` in Pydantic's model's `.dict()`.
+
+Like `item.dict(skip_defaults=True)`.
+
+That would generate a `dict` with only the data that was set when creating the `item` model, excluding default values.
+
+Then you can use this to generate a `dict` with only the data that was set, omitting default values:
+
+```Python hl_lines="34"
+{!./src/body_updates/tutorial002.py!}
+```
+
+### Using Pydantic's `update` parameter
+
+Now, you can create a copy of the existing model using `.copy()`, and pass the `update` parameter with a `dict` containing the data to update.
+
+Like `stored_item_model.copy(update=update_data)`:
+
+```Python hl_lines="35"
+{!./src/body_updates/tutorial002.py!}
+```
+
+### Partial updates recap
+
+In summary, to apply partial updates you would:
+
+* (Optionally) use `PATCH` instead of `PUT`.
+* Retrieve the stored data.
+* Put that data in a Pydantic model.
+* Generate a `dict` without default values from the input model (using `skip_defaults`).
+ * This way you can update only the values actually set by the user, instead of overriding values already stored with default values in your model.
+* Create a copy of the stored model, updating it's attributes with the received partial updates (using the `update` parameter).
+* Convert the copied model to something that can be stored in your DB (for example, using the `jsonable_encoder`).
+ * This is comparable to using the model's `.dict()` method again, but it makes sure (and converts) the values to data types that can be converted to JSON, for example, `datetime` to `str`.
+* Save the data to your DB.
+* Return the updated model.
+
+```Python hl_lines="30 31 32 33 34 35 36 37"
+{!./src/body_updates/tutorial002.py!}
+```
+
+!!! tip
+ You can actually use this same technique with an HTTP `PUT` operation.
+
+ But the example here uses `PATCH` because it was created for these use cases.
+
+!!! note
+ Notice that the input model is still validated.
+
+ So, if you want to receive partial updates that can omit all the attributes, you need to have a model with all the attributes marked as optional (with default values or `None`).
+
+ To distinguish from the models with all optional values for **updates** and models with required values for **creation**, you can use the ideas described in Extra Models.
diff --git a/docs/tutorial/response-model.md b/docs/tutorial/response-model.md
index 1402a6a8f..8b389f236 100644
--- a/docs/tutorial/response-model.md
+++ b/docs/tutorial/response-model.md
@@ -13,12 +13,14 @@ You can declare the model used for the response with the parameter `response_mod
!!! note
Notice that `response_model` is a parameter of the "decorator" method (`get`, `post`, etc). Not of your path operation function, like all the parameters and body.
-It receives a standard Pydantic model and will:
+It receives the same type you would declare for a Pydantic model attribute, so, it can be a Pydantic model, but it can also be, e.g. a `list` of Pydantic models, like `List[Item]`.
-* Convert the output data to the type declarations of the model
-* Validate the data
-* Add a JSON Schema for the response, in the OpenAPI path operation
-* Will be used by the automatic documentation systems
+FastAPI will use this `response_model` to:
+
+* Convert the output data to its type declaration.
+* Validate the data.
+* Add a JSON Schema for the response, in the OpenAPI path operation.
+* Will be used by the automatic documentation systems.
But most importantly:
@@ -45,7 +47,7 @@ Now, whenever a browser is creating a user with a password, the API will return
In this case, it might not be a problem, because the user himself is sending the password.
-But if we use the same model for another path operation, we could be sending the passwords of our users to every client.
+But if we use the same model for another path operation, we could be sending our user's passwords to every client.
!!! danger
Never send the plain password of a user in a response.
@@ -84,7 +86,7 @@ And both models will be used for the interactive API documentation:
## Response Model encoding parameters
-If your response model has default values, like:
+Your response model could have default values, like:
```Python hl_lines="11 13 14"
{!./src/response_model/tutorial004.py!}
@@ -94,6 +96,12 @@ If your response model has default values, like:
* `tax: float = None` has a default of `None`.
* `tags: List[str] = []` has a default of an empty list: `[]`.
+but you might want to omit them from the result if they were not actually stored.
+
+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.
+
+### Use the `response_model_skip_defaults` parameter
+
You can set the *path operation decorator* parameter `response_model_skip_defaults=True`:
```Python hl_lines="24"
@@ -114,7 +122,7 @@ So, if you send a request to that *path operation* for the item with ID `foo`, t
!!! info
FastAPI uses Pydantic model's `.dict()` with its `skip_defaults` parameter to achieve this.
-### Data with values for fields with defaults
+#### 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`:
@@ -129,7 +137,7 @@ But if your data has values for the model's fields with default values, like the
they will be included in the response.
-### Data with the same values as the defaults
+#### 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`:
@@ -152,34 +160,35 @@ So, they will be included in the JSON response.
They can be a list (`[]`), a `float` of `10.5`, etc.
-### Use cases
+### `response_model_include` and `response_model_exclude`
-This is very useful in several scenarios.
+You can also use the *path operation decorator* parameters `response_model_include` and `response_model_exclude`.
-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.
+They take a `set` of `str` with the name of the attributes to include (omitting the rest) or to exclude (including the rest).
-### Using Pydantic's `skip_defaults` directly
+This can be used as a quick shortcut if you have only one Pydantic model and want to remove some data from the output.
-You can also use your model's `.dict(skip_defaults=True)` in your code.
+!!! tip
+ But it is still recommended to use the ideas above, using multiple classes, instead of these parameters.
-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:
+ This is because the JSON Schema generated in your app's OpenAPI (and the docs) will still be the one for the complete model, even if you use `response_model_include` or `response_model_exclude` to omit some attributes.
-```Python hl_lines="31 32 33 34 35"
-{!./src/response_model/tutorial004.py!}
+```Python hl_lines="29 35"
+{!./src/response_model/tutorial005.py!}
```
!!! tip
- It's common to use the HTTP `PUT` operation to update data.
+ The syntax `{"name", "description"}` creates a `set` with those two values.
- In theory, `PUT` should be used to "replace" the entire contents.
+ It is equivalent to `set(["name", "description"])`.
- The less known HTTP `PATCH` operation is also used to update data.
+#### Using `list`s instead of `set`s
- But `PATCH` is expected to be used when *partially* updating data. Instead of *replacing* the entire content.
+If you forget to use a `set` and use a `list` or `tuple` instead, FastAPI will still convert it to a `set` and it will work correctly:
- 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.
+```Python hl_lines="29 35"
+{!./src/response_model/tutorial006.py!}
+```
## Recap
diff --git a/fastapi/applications.py b/fastapi/applications.py
index 3d6071ae6..0e35543dc 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -1,4 +1,4 @@
-from typing import Any, Callable, Dict, List, Optional, Type, Union
+from typing import Any, Callable, Dict, List, Optional, Set, Type, Union
from fastapi import routing
from fastapi.openapi.docs import (
@@ -138,6 +138,9 @@ class FastAPI(Starlette):
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -157,6 +160,9 @@ class FastAPI(Starlette):
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -178,6 +184,9 @@ class FastAPI(Starlette):
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -198,6 +207,9 @@ class FastAPI(Starlette):
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -250,6 +262,9 @@ class FastAPI(Starlette):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -267,6 +282,9 @@ class FastAPI(Starlette):
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -287,6 +305,9 @@ class FastAPI(Starlette):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -304,6 +325,9 @@ class FastAPI(Starlette):
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -324,6 +348,9 @@ class FastAPI(Starlette):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -341,6 +368,9 @@ class FastAPI(Starlette):
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -361,6 +391,9 @@ class FastAPI(Starlette):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -377,6 +410,9 @@ class FastAPI(Starlette):
response_description=response_description,
responses=responses or {},
deprecated=deprecated,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
operation_id=operation_id,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
@@ -398,6 +434,9 @@ class FastAPI(Starlette):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -415,6 +454,9 @@ class FastAPI(Starlette):
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -435,6 +477,9 @@ class FastAPI(Starlette):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -452,6 +497,9 @@ class FastAPI(Starlette):
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -472,6 +520,9 @@ class FastAPI(Starlette):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -489,6 +540,9 @@ class FastAPI(Starlette):
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -509,6 +563,9 @@ class FastAPI(Starlette):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -526,6 +583,9 @@ class FastAPI(Starlette):
responses=responses or {},
deprecated=deprecated,
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
diff --git a/fastapi/encoders.py b/fastapi/encoders.py
index 25ab19fa3..f5ff57f61 100644
--- a/fastapi/encoders.py
+++ b/fastapi/encoders.py
@@ -16,6 +16,10 @@ def jsonable_encoder(
custom_encoder: dict = {},
sqlalchemy_safe: bool = True,
) -> Any:
+ if include is not None and not isinstance(include, set):
+ include = set(include)
+ if exclude is not None and not isinstance(exclude, set):
+ exclude = set(exclude)
if isinstance(obj, BaseModel):
encoder = getattr(obj.Config, "json_encoders", custom_encoder)
return jsonable_encoder(
diff --git a/fastapi/routing.py b/fastapi/routing.py
index 2ae6d1e09..26d052c34 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -2,7 +2,7 @@ import asyncio
import inspect
import logging
import re
-from typing import Any, Callable, Dict, List, Optional, Type, Union
+from typing import Any, Callable, Dict, List, Optional, Set, Type, Union
from fastapi import params
from fastapi.dependencies.models import Dependant
@@ -33,10 +33,22 @@ from starlette.websockets import WebSocket
def serialize_response(
- *, field: Field = None, response: Response, skip_defaults: bool = False
+ *,
+ field: Field = None,
+ response: Response,
+ include: Set[str] = None,
+ exclude: Set[str] = set(),
+ by_alias: bool = True,
+ skip_defaults: bool = False,
) -> Any:
- encoded = jsonable_encoder(response, skip_defaults=skip_defaults)
+ encoded = jsonable_encoder(
+ response,
+ include=include,
+ exclude=exclude,
+ by_alias=by_alias,
+ skip_defaults=skip_defaults,
+ )
if field:
errors = []
value, errors_ = field.validate(encoded, {}, loc=("response",))
@@ -46,7 +58,13 @@ def serialize_response(
errors.extend(errors_)
if errors:
raise ValidationError(errors)
- return jsonable_encoder(value, skip_defaults=skip_defaults)
+ return jsonable_encoder(
+ value,
+ include=include,
+ exclude=exclude,
+ by_alias=by_alias,
+ skip_defaults=skip_defaults,
+ )
else:
return encoded
@@ -57,7 +75,10 @@ def get_app(
status_code: int = 200,
response_class: Type[Response] = JSONResponse,
response_field: Field = None,
- skip_defaults: bool = False,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
+ response_model_skip_defaults: bool = False,
) -> Callable:
assert dependant.call is not None, "dependant.call must be a function"
is_coroutine = asyncio.iscoroutinefunction(dependant.call)
@@ -97,7 +118,12 @@ def get_app(
raw_response.background = background_tasks
return raw_response
response_data = serialize_response(
- field=response_field, response=raw_response, skip_defaults=skip_defaults
+ field=response_field,
+ response=raw_response,
+ include=response_model_include,
+ exclude=response_model_exclude,
+ by_alias=response_model_by_alias,
+ skip_defaults=response_model_skip_defaults,
)
return response_class(
content=response_data,
@@ -155,6 +181,9 @@ class APIRoute(routing.Route):
name: str = None,
methods: List[str] = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -215,6 +244,9 @@ class APIRoute(routing.Route):
methods = ["GET"]
self.methods = methods
self.operation_id = operation_id
+ self.response_model_include = response_model_include
+ self.response_model_exclude = response_model_exclude
+ self.response_model_by_alias = response_model_by_alias
self.response_model_skip_defaults = response_model_skip_defaults
self.include_in_schema = include_in_schema
self.response_class = response_class
@@ -236,7 +268,10 @@ 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,
+ response_model_include=self.response_model_include,
+ response_model_exclude=self.response_model_exclude,
+ response_model_by_alias=self.response_model_by_alias,
+ response_model_skip_defaults=self.response_model_skip_defaults,
)
)
@@ -258,6 +293,9 @@ class APIRouter(routing.Router):
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -277,6 +315,9 @@ class APIRouter(routing.Router):
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -299,6 +340,9 @@ class APIRouter(routing.Router):
deprecated: bool = None,
methods: List[str] = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -319,6 +363,9 @@ class APIRouter(routing.Router):
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -374,6 +421,9 @@ class APIRouter(routing.Router):
deprecated=route.deprecated,
methods=route.methods,
operation_id=route.operation_id,
+ response_model_include=route.response_model_include,
+ response_model_exclude=route.response_model_exclude,
+ response_model_by_alias=route.response_model_by_alias,
response_model_skip_defaults=route.response_model_skip_defaults,
include_in_schema=route.include_in_schema,
response_class=route.response_class,
@@ -410,6 +460,9 @@ class APIRouter(routing.Router):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -429,6 +482,9 @@ class APIRouter(routing.Router):
deprecated=deprecated,
methods=["GET"],
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -449,6 +505,9 @@ class APIRouter(routing.Router):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -467,6 +526,9 @@ class APIRouter(routing.Router):
deprecated=deprecated,
methods=["PUT"],
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -487,6 +549,9 @@ class APIRouter(routing.Router):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -505,6 +570,9 @@ class APIRouter(routing.Router):
deprecated=deprecated,
methods=["POST"],
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -525,6 +593,9 @@ class APIRouter(routing.Router):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -543,6 +614,9 @@ class APIRouter(routing.Router):
deprecated=deprecated,
methods=["DELETE"],
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -563,6 +637,9 @@ class APIRouter(routing.Router):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -581,6 +658,9 @@ class APIRouter(routing.Router):
deprecated=deprecated,
methods=["OPTIONS"],
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -601,6 +681,9 @@ class APIRouter(routing.Router):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -619,6 +702,9 @@ class APIRouter(routing.Router):
deprecated=deprecated,
methods=["HEAD"],
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -639,6 +725,9 @@ class APIRouter(routing.Router):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -657,6 +746,9 @@ class APIRouter(routing.Router):
deprecated=deprecated,
methods=["PATCH"],
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
@@ -677,6 +769,9 @@ class APIRouter(routing.Router):
responses: Dict[Union[int, str], Dict[str, Any]] = None,
deprecated: bool = None,
operation_id: str = None,
+ response_model_include: Set[str] = None,
+ response_model_exclude: Set[str] = set(),
+ response_model_by_alias: bool = True,
response_model_skip_defaults: bool = False,
include_in_schema: bool = True,
response_class: Type[Response] = JSONResponse,
@@ -695,6 +790,9 @@ class APIRouter(routing.Router):
deprecated=deprecated,
methods=["TRACE"],
operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
response_model_skip_defaults=response_model_skip_defaults,
include_in_schema=include_in_schema,
response_class=response_class,
diff --git a/mkdocs.yml b/mkdocs.yml
index 8fa41d012..6954eb044 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -45,6 +45,7 @@ nav:
- Path Operation Advanced Configuration: 'tutorial/path-operation-advanced-configuration.md'
- Additional Status Codes: 'tutorial/additional-status-codes.md'
- JSON compatible encoder: 'tutorial/encoder.md'
+ - Body - updates: 'tutorial/body-updates.md'
- Return a Response directly: 'tutorial/response-directly.md'
- Custom Response Class: 'tutorial/custom-response.md'
- Additional Responses in OpenAPI: 'tutorial/additional-responses.md'
diff --git a/tests/test_operations_signatures.py b/tests/test_operations_signatures.py
new file mode 100644
index 000000000..1a749651d
--- /dev/null
+++ b/tests/test_operations_signatures.py
@@ -0,0 +1,22 @@
+import inspect
+
+from fastapi import APIRouter, FastAPI
+
+method_names = ["get", "put", "post", "delete", "options", "head", "patch", "trace"]
+
+
+def test_signatures_consistency():
+ base_sig = inspect.signature(APIRouter.get)
+ for method_name in method_names:
+ router_method = getattr(APIRouter, method_name)
+ app_method = getattr(FastAPI, method_name)
+ router_sig = inspect.signature(router_method)
+ app_sig = inspect.signature(app_method)
+ param: inspect.Parameter
+ for key, param in base_sig.parameters.items():
+ router_param: inspect.Parameter = router_sig.parameters[key]
+ app_param: inspect.Parameter = app_sig.parameters[key]
+ assert param.annotation == router_param.annotation
+ assert param.annotation == app_param.annotation
+ assert param.default == router_param.default
+ assert param.default == app_param.default
diff --git a/tests/test_tutorial/test_body_updates/__init__.py b/tests/test_tutorial/test_body_updates/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py
new file mode 100644
index 000000000..76a368c66
--- /dev/null
+++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py
@@ -0,0 +1,162 @@
+from starlette.testclient import TestClient
+
+from body_updates.tutorial001 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",
+ }
+ ],
+ },
+ "put": {
+ "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__put",
+ "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",
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "description": {"title": "Description", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "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
+
+
+def test_get():
+ response = client.get("/items/baz")
+ assert response.status_code == 200
+ assert response.json() == {
+ "name": "Baz",
+ "description": None,
+ "price": 50.2,
+ "tax": 10.5,
+ "tags": [],
+ }
+
+
+def test_put():
+ response = client.put(
+ "/items/bar", json={"name": "Barz", "price": 3, "description": None}
+ )
+ assert response.json() == {
+ "name": "Barz",
+ "description": None,
+ "price": 3,
+ "tax": 10.5,
+ "tags": [],
+ }
diff --git a/tests/test_tutorial/test_response_model/test_tutorial004.py b/tests/test_tutorial/test_response_model/test_tutorial004.py
index 152e291e4..c609d690d 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial004.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial004.py
@@ -41,47 +41,7 @@ openapi_schema = {
"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": {
@@ -163,15 +123,3 @@ 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,
- }
diff --git a/tests/test_tutorial/test_response_model/test_tutorial005.py b/tests/test_tutorial/test_response_model/test_tutorial005.py
new file mode 100644
index 000000000..b087395ff
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial005.py
@@ -0,0 +1,142 @@
+from starlette.testclient import TestClient
+
+from response_model.tutorial005 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "Fast API", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}/name": {
+ "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 Name",
+ "operationId": "read_item_name_items__item_id__name_get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item_Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ }
+ },
+ "/items/{item_id}/public": {
+ "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 Public Data",
+ "operationId": "read_item_public_data_items__item_id__public_get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item_Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ }
+ },
+ },
+ "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},
+ },
+ },
+ "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
+
+
+def test_read_item_name():
+ response = client.get("/items/bar/name")
+ assert response.status_code == 200
+ assert response.json() == {"name": "Bar", "description": "The Bar fighters"}
+
+
+def test_read_item_public_data():
+ response = client.get("/items/bar/public")
+ assert response.status_code == 200
+ assert response.json() == {
+ "name": "Bar",
+ "description": "The Bar fighters",
+ "price": 62,
+ }
diff --git a/tests/test_tutorial/test_response_model/test_tutorial006.py b/tests/test_tutorial/test_response_model/test_tutorial006.py
new file mode 100644
index 000000000..d7d2bce82
--- /dev/null
+++ b/tests/test_tutorial/test_response_model/test_tutorial006.py
@@ -0,0 +1,142 @@
+from starlette.testclient import TestClient
+
+from response_model.tutorial006 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "Fast API", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}/name": {
+ "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 Name",
+ "operationId": "read_item_name_items__item_id__name_get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item_Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ }
+ },
+ "/items/{item_id}/public": {
+ "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 Public Data",
+ "operationId": "read_item_public_data_items__item_id__public_get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Item_Id", "type": "string"},
+ "name": "item_id",
+ "in": "path",
+ }
+ ],
+ }
+ },
+ },
+ "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},
+ },
+ },
+ "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
+
+
+def test_read_item_name():
+ response = client.get("/items/bar/name")
+ assert response.status_code == 200
+ assert response.json() == {"name": "Bar", "description": "The Bar fighters"}
+
+
+def test_read_item_public_data():
+ response = client.get("/items/bar/public")
+ assert response.status_code == 200
+ assert response.json() == {
+ "name": "Bar",
+ "description": "The Bar fighters",
+ "price": 62,
+ }