mirror of https://github.com/tiangolo/fastapi.git
✨ Add support for `openapi_examples` in all FastAPI parameters (#10152)
* ♻️ Refactor model for OpenAPI Examples to use a reusable TypedDict * ✨ Add support for openapi_examples in parameters * 📝 Add new docs examples for new parameter openapi_examples * 📝 Update docs for Schema Extra to include OpenAPI examples * ✅ Add tests for new source examples, for openapi_examples * ✅ Add tests for openapi_examples corner cases and all parameters * 💡 Tweak and ignore type annotation checks for custom TypedDict
This commit is contained in:
parent
5f855b1179
commit
1b714b3177
|
|
@ -74,7 +74,7 @@ When using `Field()` with Pydantic models, you can also declare additional `exam
|
||||||
{!> ../../../docs_src/schema_extra_example/tutorial002.py!}
|
{!> ../../../docs_src/schema_extra_example/tutorial002.py!}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `examples` in OpenAPI
|
## `examples` in JSON Schema - OpenAPI
|
||||||
|
|
||||||
When using any of:
|
When using any of:
|
||||||
|
|
||||||
|
|
@ -86,7 +86,7 @@ When using any of:
|
||||||
* `Form()`
|
* `Form()`
|
||||||
* `File()`
|
* `File()`
|
||||||
|
|
||||||
you can also declare a group of `examples` with additional information that will be added to **OpenAPI**.
|
you can also declare a group of `examples` with additional information that will be added to their **JSON Schemas** inside of **OpenAPI**.
|
||||||
|
|
||||||
### `Body` with `examples`
|
### `Body` with `examples`
|
||||||
|
|
||||||
|
|
@ -174,9 +174,84 @@ You can of course also pass multiple `examples`:
|
||||||
{!> ../../../docs_src/schema_extra_example/tutorial004.py!}
|
{!> ../../../docs_src/schema_extra_example/tutorial004.py!}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Examples in the docs UI
|
When you do this, the examples will be part of the internal **JSON Schema** for that body data.
|
||||||
|
|
||||||
With `examples` added to `Body()` the `/docs` would look like:
|
Nevertheless, at the <abbr title="2023-08-26">time of writing this</abbr>, Swagger UI, the tool in charge of showing the docs UI, doesn't support showing multiple examples for the data in **JSON Schema**. But read below for a workaround.
|
||||||
|
|
||||||
|
### OpenAPI-specific `examples`
|
||||||
|
|
||||||
|
Since before **JSON Schema** supported `examples` OpenAPI had support for a different field also called `examples`.
|
||||||
|
|
||||||
|
This **OpenAPI-specific** `examples` goes in another section in the OpenAPI specification. It goes in the **details for each *path operation***, not inside each JSON Schema.
|
||||||
|
|
||||||
|
And Swagger UI has supported this particular `examples` field for a while. So, you can use it to **show** different **examples in the docs UI**.
|
||||||
|
|
||||||
|
The shape of this OpenAPI-specific field `examples` is a `dict` with **multiple examples** (instead of a `list`), each with extra information that will be added to **OpenAPI** too.
|
||||||
|
|
||||||
|
This doesn't go inside of each JSON Schema contained in OpenAPI, this goes outside, in the *path operation* directly.
|
||||||
|
|
||||||
|
### Using the `openapi_examples` Parameter
|
||||||
|
|
||||||
|
You can declare the OpenAPI-specific `examples` in FastAPI with the parameter `openapi_examples` for:
|
||||||
|
|
||||||
|
* `Path()`
|
||||||
|
* `Query()`
|
||||||
|
* `Header()`
|
||||||
|
* `Cookie()`
|
||||||
|
* `Body()`
|
||||||
|
* `Form()`
|
||||||
|
* `File()`
|
||||||
|
|
||||||
|
The keys of the `dict` identify each example, and each value is another `dict`.
|
||||||
|
|
||||||
|
Each specific example `dict` in the `examples` can contain:
|
||||||
|
|
||||||
|
* `summary`: Short description for the example.
|
||||||
|
* `description`: A long description that can contain Markdown text.
|
||||||
|
* `value`: This is the actual example shown, e.g. a `dict`.
|
||||||
|
* `externalValue`: alternative to `value`, a URL pointing to the example. Although this might not be supported by as many tools as `value`.
|
||||||
|
|
||||||
|
You can use it like this:
|
||||||
|
|
||||||
|
=== "Python 3.10+"
|
||||||
|
|
||||||
|
```Python hl_lines="23-49"
|
||||||
|
{!> ../../../docs_src/schema_extra_example/tutorial005_an_py310.py!}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python 3.9+"
|
||||||
|
|
||||||
|
```Python hl_lines="23-49"
|
||||||
|
{!> ../../../docs_src/schema_extra_example/tutorial005_an_py39.py!}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python 3.6+"
|
||||||
|
|
||||||
|
```Python hl_lines="24-50"
|
||||||
|
{!> ../../../docs_src/schema_extra_example/tutorial005_an.py!}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python 3.10+ non-Annotated"
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
Prefer to use the `Annotated` version if possible.
|
||||||
|
|
||||||
|
```Python hl_lines="19-45"
|
||||||
|
{!> ../../../docs_src/schema_extra_example/tutorial005_py310.py!}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Python 3.6+ non-Annotated"
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
Prefer to use the `Annotated` version if possible.
|
||||||
|
|
||||||
|
```Python hl_lines="21-47"
|
||||||
|
{!> ../../../docs_src/schema_extra_example/tutorial005.py!}
|
||||||
|
```
|
||||||
|
|
||||||
|
### OpenAPI Examples in the Docs UI
|
||||||
|
|
||||||
|
With `openapi_examples` added to `Body()` the `/docs` would look like:
|
||||||
|
|
||||||
<img src="/img/tutorial/body-fields/image02.png">
|
<img src="/img/tutorial/body-fields/image02.png">
|
||||||
|
|
||||||
|
|
@ -210,20 +285,8 @@ OpenAPI also added `example` and `examples` fields to other parts of the specifi
|
||||||
* `File()`
|
* `File()`
|
||||||
* `Form()`
|
* `Form()`
|
||||||
|
|
||||||
### OpenAPI's `examples` field
|
!!! info
|
||||||
|
This old OpenAPI-specific `examples` parameter is now `openapi_examples` since FastAPI `0.103.0`.
|
||||||
The shape of this field `examples` from OpenAPI is a `dict` with **multiple examples**, each with extra information that will be added to **OpenAPI** too.
|
|
||||||
|
|
||||||
The keys of the `dict` identify each example, and each value is another `dict`.
|
|
||||||
|
|
||||||
Each specific example `dict` in the `examples` can contain:
|
|
||||||
|
|
||||||
* `summary`: Short description for the example.
|
|
||||||
* `description`: A long description that can contain Markdown text.
|
|
||||||
* `value`: This is the actual example shown, e.g. a `dict`.
|
|
||||||
* `externalValue`: alternative to `value`, a URL pointing to the example. Although this might not be supported by as many tools as `value`.
|
|
||||||
|
|
||||||
This applies to those other parts of the OpenAPI specification apart from JSON Schema.
|
|
||||||
|
|
||||||
### JSON Schema's `examples` field
|
### JSON Schema's `examples` field
|
||||||
|
|
||||||
|
|
@ -250,6 +313,12 @@ In versions of FastAPI before 0.99.0 (0.99.0 and above use the newer OpenAPI 3.1
|
||||||
|
|
||||||
But now that FastAPI 0.99.0 and above uses OpenAPI 3.1.0, that uses JSON Schema 2020-12, and Swagger UI 5.0.0 and above, everything is more consistent and the examples are included in JSON Schema.
|
But now that FastAPI 0.99.0 and above uses OpenAPI 3.1.0, that uses JSON Schema 2020-12, and Swagger UI 5.0.0 and above, everything is more consistent and the examples are included in JSON Schema.
|
||||||
|
|
||||||
|
### Swagger UI and OpenAPI-specific `examples`
|
||||||
|
|
||||||
|
Now, as Swagger UI didn't support multiple JSON Schema examples (as of 2023-08-26), users didn't have a way to show multiple examples in the docs.
|
||||||
|
|
||||||
|
To solve that, FastAPI `0.103.0` **added support** for declaring the same old **OpenAPI-specific** `examples` field with the new parameter `openapi_examples`. 🤓
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
|
|
||||||
I used to say I didn't like history that much... and look at me now giving "tech history" lessons. 😅
|
I used to say I didn't like history that much... and look at me now giving "tech history" lessons. 😅
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from fastapi import Body, FastAPI
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
class Item(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: Union[str, None] = None
|
||||||
|
price: float
|
||||||
|
tax: Union[float, None] = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/items/{item_id}")
|
||||||
|
async def update_item(
|
||||||
|
*,
|
||||||
|
item_id: int,
|
||||||
|
item: Item = Body(
|
||||||
|
openapi_examples={
|
||||||
|
"normal": {
|
||||||
|
"summary": "A normal example",
|
||||||
|
"description": "A **normal** item works correctly.",
|
||||||
|
"value": {
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"converted": {
|
||||||
|
"summary": "An example with converted data",
|
||||||
|
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||||
|
"value": {
|
||||||
|
"name": "Bar",
|
||||||
|
"price": "35.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"invalid": {
|
||||||
|
"summary": "Invalid data is rejected with an error",
|
||||||
|
"value": {
|
||||||
|
"name": "Baz",
|
||||||
|
"price": "thirty five point four",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
):
|
||||||
|
results = {"item_id": item_id, "item": item}
|
||||||
|
return results
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from fastapi import Body, FastAPI
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
class Item(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: Union[str, None] = None
|
||||||
|
price: float
|
||||||
|
tax: Union[float, None] = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/items/{item_id}")
|
||||||
|
async def update_item(
|
||||||
|
*,
|
||||||
|
item_id: int,
|
||||||
|
item: Annotated[
|
||||||
|
Item,
|
||||||
|
Body(
|
||||||
|
openapi_examples={
|
||||||
|
"normal": {
|
||||||
|
"summary": "A normal example",
|
||||||
|
"description": "A **normal** item works correctly.",
|
||||||
|
"value": {
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"converted": {
|
||||||
|
"summary": "An example with converted data",
|
||||||
|
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||||
|
"value": {
|
||||||
|
"name": "Bar",
|
||||||
|
"price": "35.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"invalid": {
|
||||||
|
"summary": "Invalid data is rejected with an error",
|
||||||
|
"value": {
|
||||||
|
"name": "Baz",
|
||||||
|
"price": "thirty five point four",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
):
|
||||||
|
results = {"item_id": item_id, "item": item}
|
||||||
|
return results
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Body, FastAPI
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
class Item(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: str | None = None
|
||||||
|
price: float
|
||||||
|
tax: float | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/items/{item_id}")
|
||||||
|
async def update_item(
|
||||||
|
*,
|
||||||
|
item_id: int,
|
||||||
|
item: Annotated[
|
||||||
|
Item,
|
||||||
|
Body(
|
||||||
|
openapi_examples={
|
||||||
|
"normal": {
|
||||||
|
"summary": "A normal example",
|
||||||
|
"description": "A **normal** item works correctly.",
|
||||||
|
"value": {
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"converted": {
|
||||||
|
"summary": "An example with converted data",
|
||||||
|
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||||
|
"value": {
|
||||||
|
"name": "Bar",
|
||||||
|
"price": "35.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"invalid": {
|
||||||
|
"summary": "Invalid data is rejected with an error",
|
||||||
|
"value": {
|
||||||
|
"name": "Baz",
|
||||||
|
"price": "thirty five point four",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
):
|
||||||
|
results = {"item_id": item_id, "item": item}
|
||||||
|
return results
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
from typing import Annotated, Union
|
||||||
|
|
||||||
|
from fastapi import Body, FastAPI
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
class Item(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: Union[str, None] = None
|
||||||
|
price: float
|
||||||
|
tax: Union[float, None] = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/items/{item_id}")
|
||||||
|
async def update_item(
|
||||||
|
*,
|
||||||
|
item_id: int,
|
||||||
|
item: Annotated[
|
||||||
|
Item,
|
||||||
|
Body(
|
||||||
|
openapi_examples={
|
||||||
|
"normal": {
|
||||||
|
"summary": "A normal example",
|
||||||
|
"description": "A **normal** item works correctly.",
|
||||||
|
"value": {
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"converted": {
|
||||||
|
"summary": "An example with converted data",
|
||||||
|
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||||
|
"value": {
|
||||||
|
"name": "Bar",
|
||||||
|
"price": "35.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"invalid": {
|
||||||
|
"summary": "Invalid data is rejected with an error",
|
||||||
|
"value": {
|
||||||
|
"name": "Baz",
|
||||||
|
"price": "thirty five point four",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
):
|
||||||
|
results = {"item_id": item_id, "item": item}
|
||||||
|
return results
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
from fastapi import Body, FastAPI
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
class Item(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: str | None = None
|
||||||
|
price: float
|
||||||
|
tax: float | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/items/{item_id}")
|
||||||
|
async def update_item(
|
||||||
|
*,
|
||||||
|
item_id: int,
|
||||||
|
item: Item = Body(
|
||||||
|
openapi_examples={
|
||||||
|
"normal": {
|
||||||
|
"summary": "A normal example",
|
||||||
|
"description": "A **normal** item works correctly.",
|
||||||
|
"value": {
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"converted": {
|
||||||
|
"summary": "An example with converted data",
|
||||||
|
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||||
|
"value": {
|
||||||
|
"name": "Bar",
|
||||||
|
"price": "35.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"invalid": {
|
||||||
|
"summary": "Invalid data is rejected with an error",
|
||||||
|
"value": {
|
||||||
|
"name": "Baz",
|
||||||
|
"price": "thirty five point four",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
):
|
||||||
|
results = {"item_id": item_id, "item": item}
|
||||||
|
return results
|
||||||
|
|
@ -11,7 +11,7 @@ from fastapi._compat import (
|
||||||
)
|
)
|
||||||
from fastapi.logger import logger
|
from fastapi.logger import logger
|
||||||
from pydantic import AnyUrl, BaseModel, Field
|
from pydantic import AnyUrl, BaseModel, Field
|
||||||
from typing_extensions import Annotated, Literal
|
from typing_extensions import Annotated, Literal, TypedDict
|
||||||
from typing_extensions import deprecated as typing_deprecated
|
from typing_extensions import deprecated as typing_deprecated
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -267,14 +267,14 @@ class Schema(BaseModel):
|
||||||
SchemaOrBool = Union[Schema, bool]
|
SchemaOrBool = Union[Schema, bool]
|
||||||
|
|
||||||
|
|
||||||
class Example(BaseModel):
|
class Example(TypedDict, total=False):
|
||||||
summary: Optional[str] = None
|
summary: Optional[str]
|
||||||
description: Optional[str] = None
|
description: Optional[str]
|
||||||
value: Optional[Any] = None
|
value: Optional[Any]
|
||||||
externalValue: Optional[AnyUrl] = None
|
externalValue: Optional[AnyUrl]
|
||||||
|
|
||||||
if PYDANTIC_V2:
|
if PYDANTIC_V2: # type: ignore [misc]
|
||||||
model_config = {"extra": "allow"}
|
__pydantic_config__ = {"extra": "allow"}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,9 @@ def get_openapi_operation_parameters(
|
||||||
}
|
}
|
||||||
if field_info.description:
|
if field_info.description:
|
||||||
parameter["description"] = field_info.description
|
parameter["description"] = field_info.description
|
||||||
if field_info.example != Undefined:
|
if field_info.openapi_examples:
|
||||||
|
parameter["examples"] = jsonable_encoder(field_info.openapi_examples)
|
||||||
|
elif field_info.example != Undefined:
|
||||||
parameter["example"] = jsonable_encoder(field_info.example)
|
parameter["example"] = jsonable_encoder(field_info.example)
|
||||||
if field_info.deprecated:
|
if field_info.deprecated:
|
||||||
parameter["deprecated"] = field_info.deprecated
|
parameter["deprecated"] = field_info.deprecated
|
||||||
|
|
@ -153,7 +155,11 @@ def get_openapi_operation_request_body(
|
||||||
if required:
|
if required:
|
||||||
request_body_oai["required"] = required
|
request_body_oai["required"] = required
|
||||||
request_media_content: Dict[str, Any] = {"schema": body_schema}
|
request_media_content: Dict[str, Any] = {"schema": body_schema}
|
||||||
if field_info.example != Undefined:
|
if field_info.openapi_examples:
|
||||||
|
request_media_content["examples"] = jsonable_encoder(
|
||||||
|
field_info.openapi_examples
|
||||||
|
)
|
||||||
|
elif field_info.example != Undefined:
|
||||||
request_media_content["example"] = jsonable_encoder(field_info.example)
|
request_media_content["example"] = jsonable_encoder(field_info.example)
|
||||||
request_body_oai["content"] = {request_media_type: request_media_content}
|
request_body_oai["content"] = {request_media_type: request_media_content}
|
||||||
return request_body_oai
|
return request_body_oai
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from typing import Any, Callable, Dict, List, Optional, Sequence, Union
|
||||||
|
|
||||||
from fastapi import params
|
from fastapi import params
|
||||||
from fastapi._compat import Undefined
|
from fastapi._compat import Undefined
|
||||||
|
from fastapi.openapi.models import Example
|
||||||
from typing_extensions import Annotated, deprecated
|
from typing_extensions import Annotated, deprecated
|
||||||
|
|
||||||
_Unset: Any = Undefined
|
_Unset: Any = Undefined
|
||||||
|
|
@ -46,6 +47,7 @@ def Path( # noqa: N802
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -76,6 +78,7 @@ def Path( # noqa: N802
|
||||||
decimal_places=decimal_places,
|
decimal_places=decimal_places,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
|
|
@ -122,6 +125,7 @@ def Query( # noqa: N802
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -152,6 +156,7 @@ def Query( # noqa: N802
|
||||||
decimal_places=decimal_places,
|
decimal_places=decimal_places,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
|
|
@ -199,6 +204,7 @@ def Header( # noqa: N802
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -230,6 +236,7 @@ def Header( # noqa: N802
|
||||||
decimal_places=decimal_places,
|
decimal_places=decimal_places,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
|
|
@ -276,6 +283,7 @@ def Cookie( # noqa: N802
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -306,6 +314,7 @@ def Cookie( # noqa: N802
|
||||||
decimal_places=decimal_places,
|
decimal_places=decimal_places,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
|
|
@ -354,6 +363,7 @@ def Body( # noqa: N802
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -386,6 +396,7 @@ def Body( # noqa: N802
|
||||||
decimal_places=decimal_places,
|
decimal_places=decimal_places,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
|
|
@ -433,6 +444,7 @@ def Form( # noqa: N802
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -464,6 +476,7 @@ def Form( # noqa: N802
|
||||||
decimal_places=decimal_places,
|
decimal_places=decimal_places,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
|
|
@ -511,6 +524,7 @@ def File( # noqa: N802
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -542,6 +556,7 @@ def File( # noqa: N802
|
||||||
decimal_places=decimal_places,
|
decimal_places=decimal_places,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import warnings
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Callable, Dict, List, Optional, Sequence, Union
|
from typing import Any, Callable, Dict, List, Optional, Sequence, Union
|
||||||
|
|
||||||
|
from fastapi.openapi.models import Example
|
||||||
from pydantic.fields import FieldInfo
|
from pydantic.fields import FieldInfo
|
||||||
from typing_extensions import Annotated, deprecated
|
from typing_extensions import Annotated, deprecated
|
||||||
|
|
||||||
|
|
@ -61,6 +62,7 @@ class Param(FieldInfo):
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -75,6 +77,7 @@ class Param(FieldInfo):
|
||||||
)
|
)
|
||||||
self.example = example
|
self.example = example
|
||||||
self.include_in_schema = include_in_schema
|
self.include_in_schema = include_in_schema
|
||||||
|
self.openapi_examples = openapi_examples
|
||||||
kwargs = dict(
|
kwargs = dict(
|
||||||
default=default,
|
default=default,
|
||||||
default_factory=default_factory,
|
default_factory=default_factory,
|
||||||
|
|
@ -170,6 +173,7 @@ class Path(Param):
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -204,6 +208,7 @@ class Path(Param):
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
**extra,
|
**extra,
|
||||||
|
|
@ -254,6 +259,7 @@ class Query(Param):
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -286,6 +292,7 @@ class Query(Param):
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
**extra,
|
**extra,
|
||||||
|
|
@ -337,6 +344,7 @@ class Header(Param):
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -370,6 +378,7 @@ class Header(Param):
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
**extra,
|
**extra,
|
||||||
|
|
@ -420,6 +429,7 @@ class Cookie(Param):
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -452,6 +462,7 @@ class Cookie(Param):
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
**extra,
|
**extra,
|
||||||
|
|
@ -502,6 +513,7 @@ class Body(FieldInfo):
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -518,6 +530,7 @@ class Body(FieldInfo):
|
||||||
)
|
)
|
||||||
self.example = example
|
self.example = example
|
||||||
self.include_in_schema = include_in_schema
|
self.include_in_schema = include_in_schema
|
||||||
|
self.openapi_examples = openapi_examples
|
||||||
kwargs = dict(
|
kwargs = dict(
|
||||||
default=default,
|
default=default,
|
||||||
default_factory=default_factory,
|
default_factory=default_factory,
|
||||||
|
|
@ -613,6 +626,7 @@ class Form(Body):
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -647,6 +661,7 @@ class Form(Body):
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
**extra,
|
**extra,
|
||||||
|
|
@ -696,6 +711,7 @@ class File(Form):
|
||||||
"although still supported. Use examples instead."
|
"although still supported. Use examples instead."
|
||||||
),
|
),
|
||||||
] = _Unset,
|
] = _Unset,
|
||||||
|
openapi_examples: Optional[Dict[str, Example]] = None,
|
||||||
deprecated: Optional[bool] = None,
|
deprecated: Optional[bool] = None,
|
||||||
include_in_schema: bool = True,
|
include_in_schema: bool = True,
|
||||||
json_schema_extra: Union[Dict[str, Any], None] = None,
|
json_schema_extra: Union[Dict[str, Any], None] = None,
|
||||||
|
|
@ -729,6 +745,7 @@ class File(Form):
|
||||||
deprecated=deprecated,
|
deprecated=deprecated,
|
||||||
example=example,
|
example=example,
|
||||||
examples=examples,
|
examples=examples,
|
||||||
|
openapi_examples=openapi_examples,
|
||||||
include_in_schema=include_in_schema,
|
include_in_schema=include_in_schema,
|
||||||
json_schema_extra=json_schema_extra,
|
json_schema_extra=json_schema_extra,
|
||||||
**extra,
|
**extra,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,455 @@
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from dirty_equals import IsDict
|
||||||
|
from fastapi import Body, Cookie, FastAPI, Header, Path, Query
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
||||||
|
class Item(BaseModel):
|
||||||
|
data: str
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/examples/")
|
||||||
|
def examples(
|
||||||
|
item: Item = Body(
|
||||||
|
examples=[
|
||||||
|
{"data": "Data in Body examples, example1"},
|
||||||
|
],
|
||||||
|
openapi_examples={
|
||||||
|
"Example One": {
|
||||||
|
"summary": "Example One Summary",
|
||||||
|
"description": "Example One Description",
|
||||||
|
"value": {"data": "Data in Body examples, example1"},
|
||||||
|
},
|
||||||
|
"Example Two": {
|
||||||
|
"value": {"data": "Data in Body examples, example2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/path_examples/{item_id}")
|
||||||
|
def path_examples(
|
||||||
|
item_id: str = Path(
|
||||||
|
examples=[
|
||||||
|
"json_schema_item_1",
|
||||||
|
"json_schema_item_2",
|
||||||
|
],
|
||||||
|
openapi_examples={
|
||||||
|
"Path One": {
|
||||||
|
"summary": "Path One Summary",
|
||||||
|
"description": "Path One Description",
|
||||||
|
"value": "item_1",
|
||||||
|
},
|
||||||
|
"Path Two": {
|
||||||
|
"value": "item_2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
):
|
||||||
|
return item_id
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/query_examples/")
|
||||||
|
def query_examples(
|
||||||
|
data: Union[str, None] = Query(
|
||||||
|
default=None,
|
||||||
|
examples=[
|
||||||
|
"json_schema_query1",
|
||||||
|
"json_schema_query2",
|
||||||
|
],
|
||||||
|
openapi_examples={
|
||||||
|
"Query One": {
|
||||||
|
"summary": "Query One Summary",
|
||||||
|
"description": "Query One Description",
|
||||||
|
"value": "query1",
|
||||||
|
},
|
||||||
|
"Query Two": {
|
||||||
|
"value": "query2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
):
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/header_examples/")
|
||||||
|
def header_examples(
|
||||||
|
data: Union[str, None] = Header(
|
||||||
|
default=None,
|
||||||
|
examples=[
|
||||||
|
"json_schema_header1",
|
||||||
|
"json_schema_header2",
|
||||||
|
],
|
||||||
|
openapi_examples={
|
||||||
|
"Header One": {
|
||||||
|
"summary": "Header One Summary",
|
||||||
|
"description": "Header One Description",
|
||||||
|
"value": "header1",
|
||||||
|
},
|
||||||
|
"Header Two": {
|
||||||
|
"value": "header2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
):
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/cookie_examples/")
|
||||||
|
def cookie_examples(
|
||||||
|
data: Union[str, None] = Cookie(
|
||||||
|
default=None,
|
||||||
|
examples=["json_schema_cookie1", "json_schema_cookie2"],
|
||||||
|
openapi_examples={
|
||||||
|
"Cookie One": {
|
||||||
|
"summary": "Cookie One Summary",
|
||||||
|
"description": "Cookie One Description",
|
||||||
|
"value": "cookie1",
|
||||||
|
},
|
||||||
|
"Cookie Two": {
|
||||||
|
"value": "cookie2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
):
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_call_api():
|
||||||
|
response = client.get("/path_examples/foo")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
|
||||||
|
response = client.get("/query_examples/")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
|
||||||
|
response = client.get("/header_examples/")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
|
||||||
|
response = client.get("/cookie_examples/")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_openapi_schema():
|
||||||
|
response = client.get("/openapi.json")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
assert response.json() == {
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||||
|
"paths": {
|
||||||
|
"/examples/": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Examples",
|
||||||
|
"operationId": "examples_examples__post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"allOf": [{"$ref": "#/components/schemas/Item"}],
|
||||||
|
"title": "Item",
|
||||||
|
"examples": [
|
||||||
|
{"data": "Data in Body examples, example1"}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"Example One": {
|
||||||
|
"summary": "Example One Summary",
|
||||||
|
"description": "Example One Description",
|
||||||
|
"value": {
|
||||||
|
"data": "Data in Body examples, example1"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Example Two": {
|
||||||
|
"value": {
|
||||||
|
"data": "Data in Body examples, example2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {"application/json": {"schema": {}}},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/path_examples/{item_id}": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Path Examples",
|
||||||
|
"operationId": "path_examples_path_examples__item_id__get",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "item_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": True,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"json_schema_item_1",
|
||||||
|
"json_schema_item_2",
|
||||||
|
],
|
||||||
|
"title": "Item Id",
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"Path One": {
|
||||||
|
"summary": "Path One Summary",
|
||||||
|
"description": "Path One Description",
|
||||||
|
"value": "item_1",
|
||||||
|
},
|
||||||
|
"Path Two": {"value": "item_2"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {"application/json": {"schema": {}}},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/query_examples/": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Query Examples",
|
||||||
|
"operationId": "query_examples_query_examples__get",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "data",
|
||||||
|
"in": "query",
|
||||||
|
"required": False,
|
||||||
|
"schema": IsDict(
|
||||||
|
{
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||||
|
"examples": [
|
||||||
|
"json_schema_query1",
|
||||||
|
"json_schema_query2",
|
||||||
|
],
|
||||||
|
"title": "Data",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{
|
||||||
|
"examples": [
|
||||||
|
"json_schema_query1",
|
||||||
|
"json_schema_query2",
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
"title": "Data",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"examples": {
|
||||||
|
"Query One": {
|
||||||
|
"summary": "Query One Summary",
|
||||||
|
"description": "Query One Description",
|
||||||
|
"value": "query1",
|
||||||
|
},
|
||||||
|
"Query Two": {"value": "query2"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {"application/json": {"schema": {}}},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/header_examples/": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Header Examples",
|
||||||
|
"operationId": "header_examples_header_examples__get",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "data",
|
||||||
|
"in": "header",
|
||||||
|
"required": False,
|
||||||
|
"schema": IsDict(
|
||||||
|
{
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||||
|
"examples": [
|
||||||
|
"json_schema_header1",
|
||||||
|
"json_schema_header2",
|
||||||
|
],
|
||||||
|
"title": "Data",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"json_schema_header1",
|
||||||
|
"json_schema_header2",
|
||||||
|
],
|
||||||
|
"title": "Data",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"examples": {
|
||||||
|
"Header One": {
|
||||||
|
"summary": "Header One Summary",
|
||||||
|
"description": "Header One Description",
|
||||||
|
"value": "header1",
|
||||||
|
},
|
||||||
|
"Header Two": {"value": "header2"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {"application/json": {"schema": {}}},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/cookie_examples/": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Cookie Examples",
|
||||||
|
"operationId": "cookie_examples_cookie_examples__get",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "data",
|
||||||
|
"in": "cookie",
|
||||||
|
"required": False,
|
||||||
|
"schema": IsDict(
|
||||||
|
{
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||||
|
"examples": [
|
||||||
|
"json_schema_cookie1",
|
||||||
|
"json_schema_cookie2",
|
||||||
|
],
|
||||||
|
"title": "Data",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"examples": [
|
||||||
|
"json_schema_cookie1",
|
||||||
|
"json_schema_cookie2",
|
||||||
|
],
|
||||||
|
"title": "Data",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"examples": {
|
||||||
|
"Cookie One": {
|
||||||
|
"summary": "Cookie One Summary",
|
||||||
|
"description": "Cookie One Description",
|
||||||
|
"value": "cookie1",
|
||||||
|
},
|
||||||
|
"Cookie Two": {"value": "cookie2"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {"application/json": {"schema": {}}},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"HTTPValidationError": {
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Detail",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"title": "HTTPValidationError",
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"properties": {"data": {"type": "string", "title": "Data"}},
|
||||||
|
"type": "object",
|
||||||
|
"required": ["data"],
|
||||||
|
"title": "Item",
|
||||||
|
},
|
||||||
|
"ValidationError": {
|
||||||
|
"properties": {
|
||||||
|
"loc": {
|
||||||
|
"items": {
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Location",
|
||||||
|
},
|
||||||
|
"msg": {"type": "string", "title": "Message"},
|
||||||
|
"type": {"type": "string", "title": "Error Type"},
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": ["loc", "msg", "type"],
|
||||||
|
"title": "ValidationError",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
import pytest
|
||||||
|
from dirty_equals import IsDict
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="client")
|
||||||
|
def get_client():
|
||||||
|
from docs_src.schema_extra_example.tutorial005 import app
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_body_example(client: TestClient):
|
||||||
|
response = client.put(
|
||||||
|
"/items/5",
|
||||||
|
json={
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_openapi_schema(client: TestClient) -> None:
|
||||||
|
response = client.get("/openapi.json")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
assert response.json() == {
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||||
|
"paths": {
|
||||||
|
"/items/{item_id}": {
|
||||||
|
"put": {
|
||||||
|
"summary": "Update Item",
|
||||||
|
"operationId": "update_item_items__item_id__put",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"required": True,
|
||||||
|
"schema": {"title": "Item Id", "type": "integer"},
|
||||||
|
"name": "item_id",
|
||||||
|
"in": "path",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": IsDict({"$ref": "#/components/schemas/Item"})
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "#/components/schemas/Item"}
|
||||||
|
],
|
||||||
|
"title": "Item",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"examples": {
|
||||||
|
"normal": {
|
||||||
|
"summary": "A normal example",
|
||||||
|
"description": "A **normal** item works correctly.",
|
||||||
|
"value": {
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"converted": {
|
||||||
|
"summary": "An example with converted data",
|
||||||
|
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||||
|
"value": {"name": "Bar", "price": "35.4"},
|
||||||
|
},
|
||||||
|
"invalid": {
|
||||||
|
"summary": "Invalid data is rejected with an error",
|
||||||
|
"value": {
|
||||||
|
"name": "Baz",
|
||||||
|
"price": "thirty five point four",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {"application/json": {"schema": {}}},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"HTTPValidationError": {
|
||||||
|
"title": "HTTPValidationError",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"title": "Detail",
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"title": "Item",
|
||||||
|
"required": ["name", "price"],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"title": "Name", "type": "string"},
|
||||||
|
"description": IsDict(
|
||||||
|
{
|
||||||
|
"title": "Description",
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{"title": "Description", "type": "string"}
|
||||||
|
),
|
||||||
|
"price": {"title": "Price", "type": "number"},
|
||||||
|
"tax": IsDict(
|
||||||
|
{
|
||||||
|
"title": "Tax",
|
||||||
|
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{"title": "Tax", "type": "number"}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ValidationError": {
|
||||||
|
"title": "ValidationError",
|
||||||
|
"required": ["loc", "msg", "type"],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"loc": {
|
||||||
|
"title": "Location",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"msg": {"title": "Message", "type": "string"},
|
||||||
|
"type": {"title": "Error Type", "type": "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
import pytest
|
||||||
|
from dirty_equals import IsDict
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="client")
|
||||||
|
def get_client():
|
||||||
|
from docs_src.schema_extra_example.tutorial005_an import app
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_body_example(client: TestClient):
|
||||||
|
response = client.put(
|
||||||
|
"/items/5",
|
||||||
|
json={
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_openapi_schema(client: TestClient) -> None:
|
||||||
|
response = client.get("/openapi.json")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
assert response.json() == {
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||||
|
"paths": {
|
||||||
|
"/items/{item_id}": {
|
||||||
|
"put": {
|
||||||
|
"summary": "Update Item",
|
||||||
|
"operationId": "update_item_items__item_id__put",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"required": True,
|
||||||
|
"schema": {"title": "Item Id", "type": "integer"},
|
||||||
|
"name": "item_id",
|
||||||
|
"in": "path",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": IsDict({"$ref": "#/components/schemas/Item"})
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "#/components/schemas/Item"}
|
||||||
|
],
|
||||||
|
"title": "Item",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"examples": {
|
||||||
|
"normal": {
|
||||||
|
"summary": "A normal example",
|
||||||
|
"description": "A **normal** item works correctly.",
|
||||||
|
"value": {
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"converted": {
|
||||||
|
"summary": "An example with converted data",
|
||||||
|
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||||
|
"value": {"name": "Bar", "price": "35.4"},
|
||||||
|
},
|
||||||
|
"invalid": {
|
||||||
|
"summary": "Invalid data is rejected with an error",
|
||||||
|
"value": {
|
||||||
|
"name": "Baz",
|
||||||
|
"price": "thirty five point four",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {"application/json": {"schema": {}}},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"HTTPValidationError": {
|
||||||
|
"title": "HTTPValidationError",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"title": "Detail",
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"title": "Item",
|
||||||
|
"required": ["name", "price"],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"title": "Name", "type": "string"},
|
||||||
|
"description": IsDict(
|
||||||
|
{
|
||||||
|
"title": "Description",
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{"title": "Description", "type": "string"}
|
||||||
|
),
|
||||||
|
"price": {"title": "Price", "type": "number"},
|
||||||
|
"tax": IsDict(
|
||||||
|
{
|
||||||
|
"title": "Tax",
|
||||||
|
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{"title": "Tax", "type": "number"}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ValidationError": {
|
||||||
|
"title": "ValidationError",
|
||||||
|
"required": ["loc", "msg", "type"],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"loc": {
|
||||||
|
"title": "Location",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"msg": {"title": "Message", "type": "string"},
|
||||||
|
"type": {"title": "Error Type", "type": "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
import pytest
|
||||||
|
from dirty_equals import IsDict
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from ...utils import needs_py310
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="client")
|
||||||
|
def get_client():
|
||||||
|
from docs_src.schema_extra_example.tutorial005_an_py310 import app
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@needs_py310
|
||||||
|
def test_post_body_example(client: TestClient):
|
||||||
|
response = client.put(
|
||||||
|
"/items/5",
|
||||||
|
json={
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
@needs_py310
|
||||||
|
def test_openapi_schema(client: TestClient) -> None:
|
||||||
|
response = client.get("/openapi.json")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
assert response.json() == {
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||||
|
"paths": {
|
||||||
|
"/items/{item_id}": {
|
||||||
|
"put": {
|
||||||
|
"summary": "Update Item",
|
||||||
|
"operationId": "update_item_items__item_id__put",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"required": True,
|
||||||
|
"schema": {"title": "Item Id", "type": "integer"},
|
||||||
|
"name": "item_id",
|
||||||
|
"in": "path",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": IsDict({"$ref": "#/components/schemas/Item"})
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "#/components/schemas/Item"}
|
||||||
|
],
|
||||||
|
"title": "Item",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"examples": {
|
||||||
|
"normal": {
|
||||||
|
"summary": "A normal example",
|
||||||
|
"description": "A **normal** item works correctly.",
|
||||||
|
"value": {
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"converted": {
|
||||||
|
"summary": "An example with converted data",
|
||||||
|
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||||
|
"value": {"name": "Bar", "price": "35.4"},
|
||||||
|
},
|
||||||
|
"invalid": {
|
||||||
|
"summary": "Invalid data is rejected with an error",
|
||||||
|
"value": {
|
||||||
|
"name": "Baz",
|
||||||
|
"price": "thirty five point four",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {"application/json": {"schema": {}}},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"HTTPValidationError": {
|
||||||
|
"title": "HTTPValidationError",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"title": "Detail",
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"title": "Item",
|
||||||
|
"required": ["name", "price"],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"title": "Name", "type": "string"},
|
||||||
|
"description": IsDict(
|
||||||
|
{
|
||||||
|
"title": "Description",
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{"title": "Description", "type": "string"}
|
||||||
|
),
|
||||||
|
"price": {"title": "Price", "type": "number"},
|
||||||
|
"tax": IsDict(
|
||||||
|
{
|
||||||
|
"title": "Tax",
|
||||||
|
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{"title": "Tax", "type": "number"}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ValidationError": {
|
||||||
|
"title": "ValidationError",
|
||||||
|
"required": ["loc", "msg", "type"],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"loc": {
|
||||||
|
"title": "Location",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"msg": {"title": "Message", "type": "string"},
|
||||||
|
"type": {"title": "Error Type", "type": "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
import pytest
|
||||||
|
from dirty_equals import IsDict
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from ...utils import needs_py39
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="client")
|
||||||
|
def get_client():
|
||||||
|
from docs_src.schema_extra_example.tutorial005_an_py39 import app
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@needs_py39
|
||||||
|
def test_post_body_example(client: TestClient):
|
||||||
|
response = client.put(
|
||||||
|
"/items/5",
|
||||||
|
json={
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
@needs_py39
|
||||||
|
def test_openapi_schema(client: TestClient) -> None:
|
||||||
|
response = client.get("/openapi.json")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
assert response.json() == {
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||||
|
"paths": {
|
||||||
|
"/items/{item_id}": {
|
||||||
|
"put": {
|
||||||
|
"summary": "Update Item",
|
||||||
|
"operationId": "update_item_items__item_id__put",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"required": True,
|
||||||
|
"schema": {"title": "Item Id", "type": "integer"},
|
||||||
|
"name": "item_id",
|
||||||
|
"in": "path",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": IsDict({"$ref": "#/components/schemas/Item"})
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "#/components/schemas/Item"}
|
||||||
|
],
|
||||||
|
"title": "Item",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"examples": {
|
||||||
|
"normal": {
|
||||||
|
"summary": "A normal example",
|
||||||
|
"description": "A **normal** item works correctly.",
|
||||||
|
"value": {
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"converted": {
|
||||||
|
"summary": "An example with converted data",
|
||||||
|
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||||
|
"value": {"name": "Bar", "price": "35.4"},
|
||||||
|
},
|
||||||
|
"invalid": {
|
||||||
|
"summary": "Invalid data is rejected with an error",
|
||||||
|
"value": {
|
||||||
|
"name": "Baz",
|
||||||
|
"price": "thirty five point four",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {"application/json": {"schema": {}}},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"HTTPValidationError": {
|
||||||
|
"title": "HTTPValidationError",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"title": "Detail",
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"title": "Item",
|
||||||
|
"required": ["name", "price"],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"title": "Name", "type": "string"},
|
||||||
|
"description": IsDict(
|
||||||
|
{
|
||||||
|
"title": "Description",
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{"title": "Description", "type": "string"}
|
||||||
|
),
|
||||||
|
"price": {"title": "Price", "type": "number"},
|
||||||
|
"tax": IsDict(
|
||||||
|
{
|
||||||
|
"title": "Tax",
|
||||||
|
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{"title": "Tax", "type": "number"}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ValidationError": {
|
||||||
|
"title": "ValidationError",
|
||||||
|
"required": ["loc", "msg", "type"],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"loc": {
|
||||||
|
"title": "Location",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"msg": {"title": "Message", "type": "string"},
|
||||||
|
"type": {"title": "Error Type", "type": "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
import pytest
|
||||||
|
from dirty_equals import IsDict
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from ...utils import needs_py310
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="client")
|
||||||
|
def get_client():
|
||||||
|
from docs_src.schema_extra_example.tutorial005_py310 import app
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@needs_py310
|
||||||
|
def test_post_body_example(client: TestClient):
|
||||||
|
response = client.put(
|
||||||
|
"/items/5",
|
||||||
|
json={
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
@needs_py310
|
||||||
|
def test_openapi_schema(client: TestClient) -> None:
|
||||||
|
response = client.get("/openapi.json")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
assert response.json() == {
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||||
|
"paths": {
|
||||||
|
"/items/{item_id}": {
|
||||||
|
"put": {
|
||||||
|
"summary": "Update Item",
|
||||||
|
"operationId": "update_item_items__item_id__put",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"required": True,
|
||||||
|
"schema": {"title": "Item Id", "type": "integer"},
|
||||||
|
"name": "item_id",
|
||||||
|
"in": "path",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": IsDict({"$ref": "#/components/schemas/Item"})
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "#/components/schemas/Item"}
|
||||||
|
],
|
||||||
|
"title": "Item",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"examples": {
|
||||||
|
"normal": {
|
||||||
|
"summary": "A normal example",
|
||||||
|
"description": "A **normal** item works correctly.",
|
||||||
|
"value": {
|
||||||
|
"name": "Foo",
|
||||||
|
"description": "A very nice Item",
|
||||||
|
"price": 35.4,
|
||||||
|
"tax": 3.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"converted": {
|
||||||
|
"summary": "An example with converted data",
|
||||||
|
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||||
|
"value": {"name": "Bar", "price": "35.4"},
|
||||||
|
},
|
||||||
|
"invalid": {
|
||||||
|
"summary": "Invalid data is rejected with an error",
|
||||||
|
"value": {
|
||||||
|
"name": "Baz",
|
||||||
|
"price": "thirty five point four",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {"application/json": {"schema": {}}},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"HTTPValidationError": {
|
||||||
|
"title": "HTTPValidationError",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"title": "Detail",
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"title": "Item",
|
||||||
|
"required": ["name", "price"],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"title": "Name", "type": "string"},
|
||||||
|
"description": IsDict(
|
||||||
|
{
|
||||||
|
"title": "Description",
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{"title": "Description", "type": "string"}
|
||||||
|
),
|
||||||
|
"price": {"title": "Price", "type": "number"},
|
||||||
|
"tax": IsDict(
|
||||||
|
{
|
||||||
|
"title": "Tax",
|
||||||
|
"anyOf": [{"type": "number"}, {"type": "null"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| IsDict(
|
||||||
|
# TODO: remove when deprecating Pydantic v1
|
||||||
|
{"title": "Tax", "type": "number"}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ValidationError": {
|
||||||
|
"title": "ValidationError",
|
||||||
|
"required": ["loc", "msg", "type"],
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"loc": {
|
||||||
|
"title": "Location",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"msg": {"title": "Message", "type": "string"},
|
||||||
|
"type": {"title": "Error Type", "type": "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue