mirror of https://github.com/tiangolo/fastapi.git
✨ Add support for OpenAPI 3.1.0 (#9770)
* ✨ Update OpenAPI models for JSON Schema 2020-12 and OpenAPI 3.1.0 * ✨ Add support for summary and webhooks * ✨ Update JSON Schema for UploadFiles * ⏪️ Revert making paths optional, to ensure always correctness * ⏪️ Keep UploadFile as format: binary for compatibility with the rest of Pydantic bytes fields in v1 * ✨ Update version of OpenAPI generated to 3.1.0 * ✨ Update the version of Swagger UI * 📝 Update docs about extending OpenAPI * 📝 Update docs and links to refer to OpenAPI 3.1.0 * ✨ Update logic for handling webhooks * ♻️ Update parameter functions and classes, deprecate example and make examples the main field * ✅ Update tests for OpenAPI 3.1.0 * 📝 Update examples for OpenAPI metadata * ✅ Add and update tests for OpenAPI metadata * 📝 Add source example for webhooks * 📝 Update docs for metadata * 📝 Update docs for Schema extra * 📝 Add docs for webhooks * 🔧 Add webhooks docs to MkDocs * ✅ Update tests for extending OpenAPI * ✅ Add tests for webhooks * ♻️ Refactor generation of OpenAPI and JSON Schema with params * 📝 Update source examples for field examples * ✅ Update tests for examples * ➕ Make sure the minimum version of typing-extensions installed has deprecated() (already a dependency of Pydantic) * ✏️ Fix typo in Webhooks example code * 🔥 Remove commented out code of removed nullable field * 🗑️ Add deprecation warnings for example argument * ✅ Update tests to check for deprecation warnings * ✅ Add test for webhooks with security schemes, for coverage * 🍱 Update image for metadata, with new summary * 🍱 Add docs image for Webhooks * 📝 Update docs for webhooks, add docs UI image
This commit is contained in:
parent
02fc9e8a63
commit
7dad5a820b
|
|
@ -236,5 +236,5 @@ For example:
|
|||
|
||||
To see what exactly you can include in the responses, you can check these sections in the OpenAPI specification:
|
||||
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responsesObject" class="external-link" target="_blank">OpenAPI Responses Object</a>, it includes the `Response Object`.
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject" class="external-link" target="_blank">OpenAPI Response Object</a>, you can include anything from this directly in each response inside your `responses` parameter. Including `description`, `headers`, `content` (inside of this is that you declare different media types and JSON Schemas), and `links`.
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responsesObject" class="external-link" target="_blank">OpenAPI Responses Object</a>, it includes the `Response Object`.
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responseObject" class="external-link" target="_blank">OpenAPI Response Object</a>, you can include anything from this directly in each response inside your `responses` parameter. Including `description`, `headers`, `content` (inside of this is that you declare different media types and JSON Schemas), and `links`.
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ The docs UI would also need the OpenAPI schema to declare that this API `server`
|
|||
|
||||
```JSON hl_lines="4-8"
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
// More stuff here
|
||||
"servers": [
|
||||
{
|
||||
|
|
@ -298,7 +298,7 @@ Will generate an OpenAPI schema like:
|
|||
|
||||
```JSON hl_lines="5-7"
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
// More stuff here
|
||||
"servers": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,10 +29,14 @@ And that function `get_openapi()` receives as parameters:
|
|||
|
||||
* `title`: The OpenAPI title, shown in the docs.
|
||||
* `version`: The version of your API, e.g. `2.5.0`.
|
||||
* `openapi_version`: The version of the OpenAPI specification used. By default, the latest: `3.0.2`.
|
||||
* `description`: The description of your API.
|
||||
* `openapi_version`: The version of the OpenAPI specification used. By default, the latest: `3.1.0`.
|
||||
* `summary`: A short summary of the API.
|
||||
* `description`: The description of your API, this can include markdown and will be shown in the docs.
|
||||
* `routes`: A list of routes, these are each of the registered *path operations*. They are taken from `app.routes`.
|
||||
|
||||
!!! info
|
||||
The parameter `summary` is available in OpenAPI 3.1.0 and above, supported by FastAPI 0.99.0 and above.
|
||||
|
||||
## Overriding the defaults
|
||||
|
||||
Using the information above, you can use the same utility function to generate the OpenAPI schema and override each part that you need.
|
||||
|
|
@ -51,7 +55,7 @@ First, write all your **FastAPI** application as normally:
|
|||
|
||||
Then, use the same utility function to generate the OpenAPI schema, inside a `custom_openapi()` function:
|
||||
|
||||
```Python hl_lines="2 15-20"
|
||||
```Python hl_lines="2 15-21"
|
||||
{!../../../docs_src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
|
|
@ -59,7 +63,7 @@ Then, use the same utility function to generate the OpenAPI schema, inside a `cu
|
|||
|
||||
Now you can add the ReDoc extension, adding a custom `x-logo` to the `info` "object" in the OpenAPI schema:
|
||||
|
||||
```Python hl_lines="21-23"
|
||||
```Python hl_lines="22-24"
|
||||
{!../../../docs_src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
|
|
@ -71,7 +75,7 @@ That way, your application won't have to generate the schema every time a user o
|
|||
|
||||
It will be generated only once, and then the same cached schema will be used for the next requests.
|
||||
|
||||
```Python hl_lines="13-14 24-25"
|
||||
```Python hl_lines="13-14 25-26"
|
||||
{!../../../docs_src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
|
|
@ -79,7 +83,7 @@ It will be generated only once, and then the same cached schema will be used for
|
|||
|
||||
Now you can replace the `.openapi()` method with your new function.
|
||||
|
||||
```Python hl_lines="28"
|
||||
```Python hl_lines="29"
|
||||
{!../../../docs_src/extending_openapi/tutorial001.py!}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -103,11 +103,11 @@ It should look just like a normal FastAPI *path operation*:
|
|||
There are 2 main differences from a normal *path operation*:
|
||||
|
||||
* It doesn't need to have any actual code, because your app will never call this code. It's only used to document the *external API*. So, the function could just have `pass`.
|
||||
* The *path* can contain an <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#key-expression" class="external-link" target="_blank">OpenAPI 3 expression</a> (see more below) where it can use variables with parameters and parts of the original request sent to *your API*.
|
||||
* The *path* can contain an <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 expression</a> (see more below) where it can use variables with parameters and parts of the original request sent to *your API*.
|
||||
|
||||
### The callback path expression
|
||||
|
||||
The callback *path* can have an <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#key-expression" class="external-link" target="_blank">OpenAPI 3 expression</a> that can contain parts of the original request sent to *your API*.
|
||||
The callback *path* can have an <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 expression</a> that can contain parts of the original request sent to *your API*.
|
||||
|
||||
In this case, it's the `str`:
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
# OpenAPI Webhooks
|
||||
|
||||
There are cases where you want to tell your API **users** that your app could call *their* app (sending a request) with some data, normally to **notify** of some type of **event**.
|
||||
|
||||
This means that instead of the normal process of your users sending requests to your API, it's **your API** (or your app) that could **send requests to their system** (to their API, their app).
|
||||
|
||||
This is normally called a **webhook**.
|
||||
|
||||
## Webhooks steps
|
||||
|
||||
The process normally is that **you define** in your code what is the message that you will send, the **body of the request**.
|
||||
|
||||
You also define in some way at which **moments** your app will send those requests or events.
|
||||
|
||||
And **your users** define in some way (for example in a web dashboard somewhere) the **URL** where your app should send those requests.
|
||||
|
||||
All the **logic** about how to register the URLs for webhooks and the code to actually send those requests is up to you. You write it however you want to in **your own code**.
|
||||
|
||||
## Documenting webhooks with **FastAPI** and OpenAPI
|
||||
|
||||
With **FastAPI**, using OpenAPI, you can define the names of these webhooks, the types of HTTP operations that your app can send (e.g. `POST`, `PUT`, etc.) and the request **bodies** that your app would send.
|
||||
|
||||
This can make it a lot easier for your users to **implement their APIs** to receive your **webhook** requests, they might even be able to autogenerate some of their own API code.
|
||||
|
||||
!!! info
|
||||
Webhooks are available in OpenAPI 3.1.0 and above, supported by FastAPI `0.99.0` and above.
|
||||
|
||||
## An app with webhooks
|
||||
|
||||
When you create a **FastAPI** application, there is a `webhooks` attribute that you can use to define *webhooks*, the same way you would define *path operations*, for example with `@app.webhooks.post()`.
|
||||
|
||||
```Python hl_lines="9-13 36-53"
|
||||
{!../../../docs_src/openapi_webhooks/tutorial001.py!}
|
||||
```
|
||||
|
||||
The webhooks that you define will end up in the **OpenAPI** schema and the automatic **docs UI**.
|
||||
|
||||
!!! info
|
||||
The `app.webhooks` object is actually just an `APIRouter`, the same type you would use when structuring your app with multiple files.
|
||||
|
||||
Notice that with webhooks you are actually not declaring a *path* (like `/items/`), the text you pass there is just an **identifier** of the webhook (the name of the event), for example in `@app.webhooks.post("new-subscription")`, the webhook name is `new-subscription`.
|
||||
|
||||
This is because it is expected that **your users** would define the actual **URL path** where they want to receive the webhook request in some other way (e.g. a web dashboard).
|
||||
|
||||
### Check the docs
|
||||
|
||||
Now you can start your app with Uvicorn and go to <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
You will see your docs have the normal *path operations* and now also some **webhooks**:
|
||||
|
||||
<img src="/img/tutorial/openapi-webhooks/image01.png">
|
||||
|
|
@ -97,7 +97,7 @@ And if you see the resulting OpenAPI (at `/openapi.json` in your API), you will
|
|||
|
||||
```JSON hl_lines="22"
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "FastAPI",
|
||||
"version": "0.1.0"
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 84 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
|
|
@ -99,7 +99,7 @@ It will show a JSON starting with something like:
|
|||
|
||||
```JSON
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "FastAPI",
|
||||
"version": "0.1.0"
|
||||
|
|
|
|||
|
|
@ -9,15 +9,16 @@ You can set the following fields that are used in the OpenAPI specification and
|
|||
| Parameter | Type | Description |
|
||||
|------------|------|-------------|
|
||||
| `title` | `str` | The title of the API. |
|
||||
| `summary` | `str` | A short summary of the API. <small>Available since OpenAPI 3.1.0, FastAPI 0.99.0.</small> |
|
||||
| `description` | `str` | A short description of the API. It can use Markdown. |
|
||||
| `version` | `string` | The version of the API. This is the version of your own application, not of OpenAPI. For example `2.5.0`. |
|
||||
| `terms_of_service` | `str` | A URL to the Terms of Service for the API. If provided, this has to be a URL. |
|
||||
| `contact` | `dict` | The contact information for the exposed API. It can contain several fields. <details><summary><code>contact</code> fields</summary><table><thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>The identifying name of the contact person/organization.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>The URL pointing to the contact information. MUST be in the format of a URL.</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>The email address of the contact person/organization. MUST be in the format of an email address.</td></tr></tbody></table></details> |
|
||||
| `license_info` | `dict` | The license information for the exposed API. It can contain several fields. <details><summary><code>license_info</code> fields</summary><table><thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>REQUIRED</strong> (if a <code>license_info</code> is set). The license name used for the API.</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>A URL to the license used for the API. MUST be in the format of a URL.</td></tr></tbody></table></details> |
|
||||
| `license_info` | `dict` | The license information for the exposed API. It can contain several fields. <details><summary><code>license_info</code> fields</summary><table><thead><tr><th>Parameter</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>REQUIRED</strong> (if a <code>license_info</code> is set). The license name used for the API.</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>An <a href="https://spdx.dev/spdx-specification-21-web-version/#h.jxpfx0ykyb60" class="external-link" target="_blank">SPDX</a> license expression for the API. The <code>identifier</code> field is mutually exclusive of the <code>url</code> field. <small>Available since OpenAPI 3.1.0, FastAPI 0.99.0.</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>A URL to the license used for the API. MUST be in the format of a URL.</td></tr></tbody></table></details> |
|
||||
|
||||
You can set them as follows:
|
||||
|
||||
```Python hl_lines="3-16 19-31"
|
||||
```Python hl_lines="3-16 19-32"
|
||||
{!../../../docs_src/metadata/tutorial001.py!}
|
||||
```
|
||||
|
||||
|
|
@ -28,6 +29,16 @@ With this configuration, the automatic API docs would look like:
|
|||
|
||||
<img src="/img/tutorial/metadata/image01.png">
|
||||
|
||||
## License identifier
|
||||
|
||||
Since OpenAPI 3.1.0 and FastAPI 0.99.0, you can also set the `license_info` with an `identifier` instead of a `url`.
|
||||
|
||||
For example:
|
||||
|
||||
```Python hl_lines="31"
|
||||
{!../../../docs_src/metadata/tutorial001_1.py!}
|
||||
```
|
||||
|
||||
## Metadata for tags
|
||||
|
||||
You can also add additional metadata for the different tags used to group your path operations with the parameter `openapi_tags`.
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ And when you open your browser at <a href="http://127.0.0.1:8000/docs" class="ex
|
|||
|
||||
## Standards-based benefits, alternative documentation
|
||||
|
||||
And because the generated schema is from the <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md" class="external-link" target="_blank">OpenAPI</a> standard, there are many compatible tools.
|
||||
And because the generated schema is from the <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md" class="external-link" target="_blank">OpenAPI</a> standard, there are many compatible tools.
|
||||
|
||||
Because of this, **FastAPI** itself provides an alternative API documentation (using ReDoc), which you can access at <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>:
|
||||
|
||||
|
|
|
|||
|
|
@ -6,17 +6,17 @@ Here are several ways to do it.
|
|||
|
||||
## Pydantic `schema_extra`
|
||||
|
||||
You can declare an `example` for a Pydantic model using `Config` and `schema_extra`, as described in <a href="https://pydantic-docs.helpmanual.io/usage/schema/#schema-customization" class="external-link" target="_blank">Pydantic's docs: Schema customization</a>:
|
||||
You can declare `examples` for a Pydantic model using `Config` and `schema_extra`, as described in <a href="https://pydantic-docs.helpmanual.io/usage/schema/#schema-customization" class="external-link" target="_blank">Pydantic's docs: Schema customization</a>:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="13-21"
|
||||
```Python hl_lines="13-23"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="15-23"
|
||||
```Python hl_lines="15-25"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial001.py!}
|
||||
```
|
||||
|
||||
|
|
@ -27,11 +27,16 @@ That extra info will be added as-is to the output **JSON Schema** for that model
|
|||
|
||||
For example you could use it to add metadata for a frontend user interface, etc.
|
||||
|
||||
!!! info
|
||||
OpenAPI 3.1.0 (used since FastAPI 0.99.0) added support for `examples`, which is part of the **JSON Schema** standard.
|
||||
|
||||
Before that, it only supported the keyword `example` with a single example. That is still supported by OpenAPI 3.1.0, but is deprecated and is not part of the JSON Schema standard. So you are encouraged to migrate `example` to `examples`. 🤓
|
||||
|
||||
You can read more at the end of this page.
|
||||
|
||||
## `Field` additional arguments
|
||||
|
||||
When using `Field()` with Pydantic models, you can also declare extra info for the **JSON Schema** by passing any other arbitrary arguments to the function.
|
||||
|
||||
You can use this to add `example` for each field:
|
||||
When using `Field()` with Pydantic models, you can also declare additional `examples`:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
|
|
@ -45,10 +50,7 @@ You can use this to add `example` for each field:
|
|||
{!> ../../../docs_src/schema_extra_example/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Keep in mind that those extra arguments passed won't add any validation, only extra information, for documentation purposes.
|
||||
|
||||
## `example` and `examples` in OpenAPI
|
||||
## `examples` in OpenAPI
|
||||
|
||||
When using any of:
|
||||
|
||||
|
|
@ -60,27 +62,27 @@ When using any of:
|
|||
* `Form()`
|
||||
* `File()`
|
||||
|
||||
you can also declare a data `example` or 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 **OpenAPI**.
|
||||
|
||||
### `Body` with `example`
|
||||
### `Body` with `examples`
|
||||
|
||||
Here we pass an `example` of the data expected in `Body()`:
|
||||
Here we pass `examples` containing one example of the data expected in `Body()`:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
```Python hl_lines="22-27"
|
||||
```Python hl_lines="22-29"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial003_an_py310.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.9+"
|
||||
|
||||
```Python hl_lines="22-27"
|
||||
```Python hl_lines="22-29"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial003_an_py39.py!}
|
||||
```
|
||||
|
||||
=== "Python 3.6+"
|
||||
|
||||
```Python hl_lines="23-28"
|
||||
```Python hl_lines="23-30"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial003_an.py!}
|
||||
```
|
||||
|
||||
|
|
@ -89,7 +91,7 @@ Here we pass an `example` of the data expected in `Body()`:
|
|||
!!! tip
|
||||
Prefer to use the `Annotated` version if possible.
|
||||
|
||||
```Python hl_lines="18-23"
|
||||
```Python hl_lines="18-25"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!}
|
||||
```
|
||||
|
||||
|
|
@ -98,7 +100,7 @@ Here we pass an `example` of the data expected in `Body()`:
|
|||
!!! tip
|
||||
Prefer to use the `Annotated` version if possible.
|
||||
|
||||
```Python hl_lines="20-25"
|
||||
```Python hl_lines="20-27"
|
||||
{!> ../../../docs_src/schema_extra_example/tutorial003.py!}
|
||||
```
|
||||
|
||||
|
|
@ -110,16 +112,7 @@ With any of the methods above it would look like this in the `/docs`:
|
|||
|
||||
### `Body` with multiple `examples`
|
||||
|
||||
Alternatively to the single `example`, you can pass `examples` using 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`.
|
||||
You can of course also pass multiple `examples`:
|
||||
|
||||
=== "Python 3.10+"
|
||||
|
||||
|
|
@ -165,25 +158,76 @@ With `examples` added to `Body()` the `/docs` would look like:
|
|||
|
||||
## Technical Details
|
||||
|
||||
!!! tip
|
||||
If you are already using **FastAPI** version **0.99.0 or above**, you can probably **skip** these details.
|
||||
|
||||
They are more relevant for older versions, before OpenAPI 3.1.0 was available.
|
||||
|
||||
You can consider this a brief OpenAPI and JSON Schema **history lesson**. 🤓
|
||||
|
||||
!!! warning
|
||||
These are very technical details about the standards **JSON Schema** and **OpenAPI**.
|
||||
|
||||
If the ideas above already work for you, that might be enough, and you probably don't need these details, feel free to skip them.
|
||||
|
||||
When you add an example inside of a Pydantic model, using `schema_extra` or `Field(example="something")` that example is added to the **JSON Schema** for that Pydantic model.
|
||||
Before OpenAPI 3.1.0, OpenAPI used an older and modified version of **JSON Schema**.
|
||||
|
||||
JSON Schema didn't have `examples`, so OpenAPI added it's own `example` field to its own modified version.
|
||||
|
||||
OpenAPI also added `example` and `examples` fields to other parts of the specification:
|
||||
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object" class="external-link" target="_blank">`Parameter Object` (in the specification)</a> that was used by FastAPI's:
|
||||
* `Path()`
|
||||
* `Query()`
|
||||
* `Header()`
|
||||
* `Cookie()`
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object" class="external-link" target="_blank">`Request Body Object`, in the field `content`, on the `Media Type Object` (in the specification)</a> that was used by FastAPI's:
|
||||
* `Body()`
|
||||
* `File()`
|
||||
* `Form()`
|
||||
|
||||
### OpenAPI's `examples` field
|
||||
|
||||
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
|
||||
|
||||
But then JSON Schema added an <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a> field to a new version of the specification.
|
||||
|
||||
And then the new OpenAPI 3.1.0 was based on the latest version (JSON Schema 2020-12) that included this new field `examples`.
|
||||
|
||||
And now this new `examples` field takes precedence over the old single (and custom) `example` field, that is now deprecated.
|
||||
|
||||
This new `examples` field in JSON Schema is **just a `list`** of examples, not a dict with extra metadata as in the other places in OpenAPI (described above).
|
||||
|
||||
!!! info
|
||||
Even after OpenAPI 3.1.0 was released with this new simpler integration with JSON Schema, for a while, Swagger UI, the tool that provides the automatic docs, didn't support OpenAPI 3.1.0 (it does since version 5.0.0 🎉).
|
||||
|
||||
Because of that, versions of FastAPI previous to 0.99.0 still used versions of OpenAPI lower than 3.1.0.
|
||||
|
||||
### Pydantic and FastAPI `examples`
|
||||
|
||||
When you add `examples` inside of a Pydantic model, using `schema_extra` or `Field(examples=["something"])` that example is added to the **JSON Schema** for that Pydantic model.
|
||||
|
||||
And that **JSON Schema** of the Pydantic model is included in the **OpenAPI** of your API, and then it's used in the docs UI.
|
||||
|
||||
**JSON Schema** doesn't really have a field `example` in the standards. Recent versions of JSON Schema define a field <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a>, but OpenAPI 3.0.3 is based on an older version of JSON Schema that didn't have `examples`.
|
||||
In versions of FastAPI before 0.99.0 (0.99.0 and above use the newer OpenAPI 3.1.0) when you used `example` or `examples` with any of the other utilities (`Query()`, `Body()`, etc.) those examples were not added to the JSON Schema that describes that data (not even to OpenAPI's own version of JSON Schema), they were added directly to the *path operation* declaration in OpenAPI (outside the parts of OpenAPI that use JSON Schema).
|
||||
|
||||
So, OpenAPI 3.0.3 defined its own <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-20" class="external-link" target="_blank">`example`</a> for the modified version of **JSON Schema** it uses, for the same purpose (but it's a single `example`, not `examples`), and that's what is used by the API docs UI (using Swagger UI).
|
||||
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.
|
||||
|
||||
So, although `example` is not part of JSON Schema, it is part of OpenAPI's custom version of JSON Schema, and that's what will be used by the docs UI.
|
||||
### Summary
|
||||
|
||||
But when you use `example` or `examples` with any of the other utilities (`Query()`, `Body()`, etc.) those examples are not added to the JSON Schema that describes that data (not even to OpenAPI's own version of JSON Schema), they are added directly to the *path operation* declaration in OpenAPI (outside the parts of OpenAPI that use JSON Schema).
|
||||
I used to say I didn't like history that much... and look at me now giving "tech history" lessons. 😅
|
||||
|
||||
For `Path()`, `Query()`, `Header()`, and `Cookie()`, the `example` or `examples` are added to the <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object" class="external-link" target="_blank">OpenAPI definition, to the `Parameter Object` (in the specification)</a>.
|
||||
|
||||
And for `Body()`, `File()`, and `Form()`, the `example` or `examples` are equivalently added to the <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#mediaTypeObject" class="external-link" target="_blank">OpenAPI definition, to the `Request Body Object`, in the field `content`, on the `Media Type Object` (in the specification)</a>.
|
||||
|
||||
On the other hand, there's a newer version of OpenAPI: **3.1.0**, recently released. It is based on the latest JSON Schema and most of the modifications from OpenAPI's custom version of JSON Schema are removed, in exchange of the features from the recent versions of JSON Schema, so all these small differences are reduced. Nevertheless, Swagger UI currently doesn't support OpenAPI 3.1.0, so, for now, it's better to continue using the ideas above.
|
||||
In short, **upgrade to FastAPI 0.99.0 or above**, and things are much **simpler, consistent, and intuitive**, and you don't have to know all these historic details. 😎
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ nav:
|
|||
- advanced/conditional-openapi.md
|
||||
- advanced/extending-openapi.md
|
||||
- advanced/openapi-callbacks.md
|
||||
- advanced/openapi-webhooks.md
|
||||
- advanced/wsgi.md
|
||||
- advanced/generate-clients.md
|
||||
- async.md
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ def custom_openapi():
|
|||
openapi_schema = get_openapi(
|
||||
title="Custom title",
|
||||
version="2.5.0",
|
||||
description="This is a very custom OpenAPI schema",
|
||||
summary="This is a very custom OpenAPI schema",
|
||||
description="Here's a longer description of the custom **OpenAPI** schema",
|
||||
routes=app.routes,
|
||||
)
|
||||
openapi_schema["info"]["x-logo"] = {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ You will be able to:
|
|||
app = FastAPI(
|
||||
title="ChimichangApp",
|
||||
description=description,
|
||||
summary="Deadpool's favorite app. Nuff said.",
|
||||
version="0.0.1",
|
||||
terms_of_service="http://example.com/terms/",
|
||||
contact={
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
from fastapi import FastAPI
|
||||
|
||||
description = """
|
||||
ChimichangApp API helps you do awesome stuff. 🚀
|
||||
|
||||
## Items
|
||||
|
||||
You can **read items**.
|
||||
|
||||
## Users
|
||||
|
||||
You will be able to:
|
||||
|
||||
* **Create users** (_not implemented_).
|
||||
* **Read users** (_not implemented_).
|
||||
"""
|
||||
|
||||
app = FastAPI(
|
||||
title="ChimichangApp",
|
||||
description=description,
|
||||
summary="Deadpool's favorite app. Nuff said.",
|
||||
version="0.0.1",
|
||||
terms_of_service="http://example.com/terms/",
|
||||
contact={
|
||||
"name": "Deadpoolio the Amazing",
|
||||
"url": "http://x-force.example.com/contact/",
|
||||
"email": "dp@x-force.example.com",
|
||||
},
|
||||
license_info={
|
||||
"name": "Apache 2.0",
|
||||
"identifier": "MIT",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items():
|
||||
return [{"name": "Katana"}]
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
from datetime import datetime
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Subscription(BaseModel):
|
||||
username: str
|
||||
montly_fee: float
|
||||
start_date: datetime
|
||||
|
||||
|
||||
@app.webhooks.post("new-subscription")
|
||||
def new_subscription(body: Subscription):
|
||||
"""
|
||||
When a new user subscribes to your service we'll send you a POST request with this
|
||||
data to the URL that you register for the event `new-subscription` in the dashboard.
|
||||
"""
|
||||
|
||||
|
||||
@app.get("/users/")
|
||||
def read_users():
|
||||
return ["Rick", "Morty"]
|
||||
|
|
@ -14,12 +14,14 @@ class Item(BaseModel):
|
|||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
}
|
||||
"examples": [
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ class Item(BaseModel):
|
|||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
}
|
||||
"examples": [
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ app = FastAPI()
|
|||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str = Field(example="Foo")
|
||||
description: Union[str, None] = Field(default=None, example="A very nice Item")
|
||||
price: float = Field(example=35.4)
|
||||
tax: Union[float, None] = Field(default=None, example=3.2)
|
||||
name: str = Field(examples=["Foo"])
|
||||
description: Union[str, None] = Field(default=None, examples=["A very nice Item"])
|
||||
price: float = Field(examples=[35.4])
|
||||
tax: Union[float, None] = Field(default=None, examples=[3.2])
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ app = FastAPI()
|
|||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str = Field(example="Foo")
|
||||
description: str | None = Field(default=None, example="A very nice Item")
|
||||
price: float = Field(example=35.4)
|
||||
tax: float | None = Field(default=None, example=3.2)
|
||||
name: str = Field(examples=["Foo"])
|
||||
description: str | None = Field(default=None, examples=["A very nice Item"])
|
||||
price: float = Field(examples=[35.4])
|
||||
tax: float | None = Field(default=None, examples=[3.2])
|
||||
|
||||
|
||||
@app.put("/items/{item_id}")
|
||||
|
|
|
|||
|
|
@ -17,12 +17,14 @@ class Item(BaseModel):
|
|||
async def update_item(
|
||||
item_id: int,
|
||||
item: Item = Body(
|
||||
example={
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
},
|
||||
examples=[
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
}
|
||||
],
|
||||
),
|
||||
):
|
||||
results = {"item_id": item_id, "item": item}
|
||||
|
|
|
|||
|
|
@ -20,12 +20,14 @@ async def update_item(
|
|||
item: Annotated[
|
||||
Item,
|
||||
Body(
|
||||
example={
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
},
|
||||
examples=[
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
}
|
||||
],
|
||||
),
|
||||
],
|
||||
):
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ async def update_item(
|
|||
item: Annotated[
|
||||
Item,
|
||||
Body(
|
||||
example={
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
},
|
||||
examples=[
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
}
|
||||
],
|
||||
),
|
||||
],
|
||||
):
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ async def update_item(
|
|||
item: Annotated[
|
||||
Item,
|
||||
Body(
|
||||
example={
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
},
|
||||
examples=[
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
}
|
||||
],
|
||||
),
|
||||
],
|
||||
):
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ class Item(BaseModel):
|
|||
async def update_item(
|
||||
item_id: int,
|
||||
item: Item = Body(
|
||||
example={
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
},
|
||||
examples=[
|
||||
{
|
||||
"name": "Foo",
|
||||
"description": "A very nice Item",
|
||||
"price": 35.4,
|
||||
"tax": 3.2,
|
||||
}
|
||||
],
|
||||
),
|
||||
):
|
||||
results = {"item_id": item_id, "item": item}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ async def update_item(
|
|||
*,
|
||||
item_id: int,
|
||||
item: Item = Body(
|
||||
examples={
|
||||
"normal": {
|
||||
examples=[
|
||||
{
|
||||
"summary": "A normal example",
|
||||
"description": "A **normal** item works correctly.",
|
||||
"value": {
|
||||
|
|
@ -29,7 +29,7 @@ async def update_item(
|
|||
"tax": 3.2,
|
||||
},
|
||||
},
|
||||
"converted": {
|
||||
{
|
||||
"summary": "An example with converted data",
|
||||
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||
"value": {
|
||||
|
|
@ -37,14 +37,14 @@ async def update_item(
|
|||
"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}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ async def update_item(
|
|||
item: Annotated[
|
||||
Item,
|
||||
Body(
|
||||
examples={
|
||||
"normal": {
|
||||
examples=[
|
||||
{
|
||||
"summary": "A normal example",
|
||||
"description": "A **normal** item works correctly.",
|
||||
"value": {
|
||||
|
|
@ -32,7 +32,7 @@ async def update_item(
|
|||
"tax": 3.2,
|
||||
},
|
||||
},
|
||||
"converted": {
|
||||
{
|
||||
"summary": "An example with converted data",
|
||||
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||
"value": {
|
||||
|
|
@ -40,14 +40,14 @@ async def update_item(
|
|||
"price": "35.4",
|
||||
},
|
||||
},
|
||||
"invalid": {
|
||||
{
|
||||
"summary": "Invalid data is rejected with an error",
|
||||
"value": {
|
||||
"name": "Baz",
|
||||
"price": "thirty five point four",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
):
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ async def update_item(
|
|||
item: Annotated[
|
||||
Item,
|
||||
Body(
|
||||
examples={
|
||||
"normal": {
|
||||
examples=[
|
||||
{
|
||||
"summary": "A normal example",
|
||||
"description": "A **normal** item works correctly.",
|
||||
"value": {
|
||||
|
|
@ -31,7 +31,7 @@ async def update_item(
|
|||
"tax": 3.2,
|
||||
},
|
||||
},
|
||||
"converted": {
|
||||
{
|
||||
"summary": "An example with converted data",
|
||||
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||
"value": {
|
||||
|
|
@ -39,14 +39,14 @@ async def update_item(
|
|||
"price": "35.4",
|
||||
},
|
||||
},
|
||||
"invalid": {
|
||||
{
|
||||
"summary": "Invalid data is rejected with an error",
|
||||
"value": {
|
||||
"name": "Baz",
|
||||
"price": "thirty five point four",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
):
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ async def update_item(
|
|||
item: Annotated[
|
||||
Item,
|
||||
Body(
|
||||
examples={
|
||||
"normal": {
|
||||
examples=[
|
||||
{
|
||||
"summary": "A normal example",
|
||||
"description": "A **normal** item works correctly.",
|
||||
"value": {
|
||||
|
|
@ -31,7 +31,7 @@ async def update_item(
|
|||
"tax": 3.2,
|
||||
},
|
||||
},
|
||||
"converted": {
|
||||
{
|
||||
"summary": "An example with converted data",
|
||||
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||
"value": {
|
||||
|
|
@ -39,14 +39,14 @@ async def update_item(
|
|||
"price": "35.4",
|
||||
},
|
||||
},
|
||||
"invalid": {
|
||||
{
|
||||
"summary": "Invalid data is rejected with an error",
|
||||
"value": {
|
||||
"name": "Baz",
|
||||
"price": "thirty five point four",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
),
|
||||
],
|
||||
):
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ async def update_item(
|
|||
*,
|
||||
item_id: int,
|
||||
item: Item = Body(
|
||||
examples={
|
||||
"normal": {
|
||||
examples=[
|
||||
{
|
||||
"summary": "A normal example",
|
||||
"description": "A **normal** item works correctly.",
|
||||
"value": {
|
||||
|
|
@ -27,7 +27,7 @@ async def update_item(
|
|||
"tax": 3.2,
|
||||
},
|
||||
},
|
||||
"converted": {
|
||||
{
|
||||
"summary": "An example with converted data",
|
||||
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
|
||||
"value": {
|
||||
|
|
@ -35,14 +35,14 @@ async def update_item(
|
|||
"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}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ class FastAPI(Starlette):
|
|||
debug: bool = False,
|
||||
routes: Optional[List[BaseRoute]] = None,
|
||||
title: str = "FastAPI",
|
||||
summary: Optional[str] = None,
|
||||
description: str = "",
|
||||
version: str = "0.1.0",
|
||||
openapi_url: Optional[str] = "/openapi.json",
|
||||
|
|
@ -85,6 +86,7 @@ class FastAPI(Starlette):
|
|||
root_path_in_servers: bool = True,
|
||||
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
|
||||
callbacks: Optional[List[BaseRoute]] = None,
|
||||
webhooks: Optional[routing.APIRouter] = None,
|
||||
deprecated: Optional[bool] = None,
|
||||
include_in_schema: bool = True,
|
||||
swagger_ui_parameters: Optional[Dict[str, Any]] = None,
|
||||
|
|
@ -95,6 +97,7 @@ class FastAPI(Starlette):
|
|||
) -> None:
|
||||
self.debug = debug
|
||||
self.title = title
|
||||
self.summary = summary
|
||||
self.description = description
|
||||
self.version = version
|
||||
self.terms_of_service = terms_of_service
|
||||
|
|
@ -110,7 +113,7 @@ class FastAPI(Starlette):
|
|||
self.swagger_ui_parameters = swagger_ui_parameters
|
||||
self.servers = servers or []
|
||||
self.extra = extra
|
||||
self.openapi_version = "3.0.2"
|
||||
self.openapi_version = "3.1.0"
|
||||
self.openapi_schema: Optional[Dict[str, Any]] = None
|
||||
if self.openapi_url:
|
||||
assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'"
|
||||
|
|
@ -123,6 +126,7 @@ class FastAPI(Starlette):
|
|||
"automatic. Check the docs at "
|
||||
"https://fastapi.tiangolo.com/advanced/sub-applications/"
|
||||
)
|
||||
self.webhooks = webhooks or routing.APIRouter()
|
||||
self.root_path = root_path or openapi_prefix
|
||||
self.state: State = State()
|
||||
self.dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {}
|
||||
|
|
@ -215,11 +219,13 @@ class FastAPI(Starlette):
|
|||
title=self.title,
|
||||
version=self.version,
|
||||
openapi_version=self.openapi_version,
|
||||
summary=self.summary,
|
||||
description=self.description,
|
||||
terms_of_service=self.terms_of_service,
|
||||
contact=self.contact,
|
||||
license_info=self.license_info,
|
||||
routes=self.routes,
|
||||
webhooks=self.webhooks.routes,
|
||||
tags=self.openapi_tags,
|
||||
servers=self.servers,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ def get_swagger_ui_html(
|
|||
*,
|
||||
openapi_url: str,
|
||||
title: str,
|
||||
swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui-bundle.js",
|
||||
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui.css",
|
||||
swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
|
||||
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css",
|
||||
swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
|
||||
oauth2_redirect_url: Optional[str] = None,
|
||||
init_oauth: Optional[Dict[str, Any]] = None,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
from enum import Enum
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Union
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Union
|
||||
|
||||
from fastapi.logger import logger
|
||||
from pydantic import AnyUrl, BaseModel, Field
|
||||
from typing_extensions import Literal
|
||||
from typing_extensions import Annotated, Literal
|
||||
from typing_extensions import deprecated as typing_deprecated
|
||||
|
||||
try:
|
||||
import email_validator # type: ignore
|
||||
|
|
@ -37,6 +38,7 @@ class Contact(BaseModel):
|
|||
|
||||
class License(BaseModel):
|
||||
name: str
|
||||
identifier: Optional[str] = None
|
||||
url: Optional[AnyUrl] = None
|
||||
|
||||
class Config:
|
||||
|
|
@ -45,6 +47,7 @@ class License(BaseModel):
|
|||
|
||||
class Info(BaseModel):
|
||||
title: str
|
||||
summary: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
termsOfService: Optional[str] = None
|
||||
contact: Optional[Contact] = None
|
||||
|
|
@ -56,7 +59,7 @@ class Info(BaseModel):
|
|||
|
||||
|
||||
class ServerVariable(BaseModel):
|
||||
enum: Optional[List[str]] = None
|
||||
enum: Annotated[Optional[List[str]], Field(min_items=1)] = None
|
||||
default: str
|
||||
description: Optional[str] = None
|
||||
|
||||
|
|
@ -102,9 +105,42 @@ class ExternalDocumentation(BaseModel):
|
|||
|
||||
|
||||
class Schema(BaseModel):
|
||||
# Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu
|
||||
# Core Vocabulary
|
||||
schema_: Optional[str] = Field(default=None, alias="$schema")
|
||||
vocabulary: Optional[str] = Field(default=None, alias="$vocabulary")
|
||||
id: Optional[str] = Field(default=None, alias="$id")
|
||||
anchor: Optional[str] = Field(default=None, alias="$anchor")
|
||||
dynamicAnchor: Optional[str] = Field(default=None, alias="$dynamicAnchor")
|
||||
ref: Optional[str] = Field(default=None, alias="$ref")
|
||||
title: Optional[str] = None
|
||||
multipleOf: Optional[float] = None
|
||||
dynamicRef: Optional[str] = Field(default=None, alias="$dynamicRef")
|
||||
defs: Optional[Dict[str, "Schema"]] = Field(default=None, alias="$defs")
|
||||
comment: Optional[str] = Field(default=None, alias="$comment")
|
||||
# Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-a-vocabulary-for-applying-s
|
||||
# A Vocabulary for Applying Subschemas
|
||||
allOf: Optional[List["Schema"]] = None
|
||||
anyOf: Optional[List["Schema"]] = None
|
||||
oneOf: Optional[List["Schema"]] = None
|
||||
not_: Optional["Schema"] = Field(default=None, alias="not")
|
||||
if_: Optional["Schema"] = Field(default=None, alias="if")
|
||||
then: Optional["Schema"] = None
|
||||
else_: Optional["Schema"] = Field(default=None, alias="else")
|
||||
dependentSchemas: Optional[Dict[str, "Schema"]] = None
|
||||
prefixItems: Optional[List["Schema"]] = None
|
||||
items: Optional[Union["Schema", List["Schema"]]] = None
|
||||
contains: Optional["Schema"] = None
|
||||
properties: Optional[Dict[str, "Schema"]] = None
|
||||
patternProperties: Optional[Dict[str, "Schema"]] = None
|
||||
additionalProperties: Optional["Schema"] = None
|
||||
propertyNames: Optional["Schema"] = None
|
||||
unevaluatedItems: Optional["Schema"] = None
|
||||
unevaluatedProperties: Optional["Schema"] = None
|
||||
# Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural
|
||||
# A Vocabulary for Structural Validation
|
||||
type: Optional[str] = None
|
||||
enum: Optional[List[Any]] = None
|
||||
const: Optional[Any] = None
|
||||
multipleOf: Optional[float] = Field(default=None, gt=0)
|
||||
maximum: Optional[float] = None
|
||||
exclusiveMaximum: Optional[float] = None
|
||||
minimum: Optional[float] = None
|
||||
|
|
@ -115,29 +151,41 @@ class Schema(BaseModel):
|
|||
maxItems: Optional[int] = Field(default=None, ge=0)
|
||||
minItems: Optional[int] = Field(default=None, ge=0)
|
||||
uniqueItems: Optional[bool] = None
|
||||
maxContains: Optional[int] = Field(default=None, ge=0)
|
||||
minContains: Optional[int] = Field(default=None, ge=0)
|
||||
maxProperties: Optional[int] = Field(default=None, ge=0)
|
||||
minProperties: Optional[int] = Field(default=None, ge=0)
|
||||
required: Optional[List[str]] = None
|
||||
enum: Optional[List[Any]] = None
|
||||
type: Optional[str] = None
|
||||
allOf: Optional[List["Schema"]] = None
|
||||
oneOf: Optional[List["Schema"]] = None
|
||||
anyOf: Optional[List["Schema"]] = None
|
||||
not_: Optional["Schema"] = Field(default=None, alias="not")
|
||||
items: Optional[Union["Schema", List["Schema"]]] = None
|
||||
properties: Optional[Dict[str, "Schema"]] = None
|
||||
additionalProperties: Optional[Union["Schema", Reference, bool]] = None
|
||||
description: Optional[str] = None
|
||||
dependentRequired: Optional[Dict[str, Set[str]]] = None
|
||||
# Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-vocabularies-for-semantic-c
|
||||
# Vocabularies for Semantic Content With "format"
|
||||
format: Optional[str] = None
|
||||
# Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-the-conten
|
||||
# A Vocabulary for the Contents of String-Encoded Data
|
||||
contentEncoding: Optional[str] = None
|
||||
contentMediaType: Optional[str] = None
|
||||
contentSchema: Optional["Schema"] = None
|
||||
# Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta
|
||||
# A Vocabulary for Basic Meta-Data Annotations
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
default: Optional[Any] = None
|
||||
nullable: Optional[bool] = None
|
||||
discriminator: Optional[Discriminator] = None
|
||||
deprecated: Optional[bool] = None
|
||||
readOnly: Optional[bool] = None
|
||||
writeOnly: Optional[bool] = None
|
||||
examples: Optional[List[Any]] = None
|
||||
# Ref: OpenAPI 3.1.0: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object
|
||||
# Schema Object
|
||||
discriminator: Optional[Discriminator] = None
|
||||
xml: Optional[XML] = None
|
||||
externalDocs: Optional[ExternalDocumentation] = None
|
||||
example: Optional[Any] = None
|
||||
deprecated: Optional[bool] = None
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
typing_deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = None
|
||||
|
||||
class Config:
|
||||
extra: str = "allow"
|
||||
|
|
@ -248,7 +296,7 @@ class Operation(BaseModel):
|
|||
parameters: Optional[List[Union[Parameter, Reference]]] = None
|
||||
requestBody: Optional[Union[RequestBody, Reference]] = None
|
||||
# Using Any for Specification Extensions
|
||||
responses: Dict[str, Union[Response, Any]]
|
||||
responses: Optional[Dict[str, Union[Response, Any]]] = None
|
||||
callbacks: Optional[Dict[str, Union[Dict[str, "PathItem"], Reference]]] = None
|
||||
deprecated: Optional[bool] = None
|
||||
security: Optional[List[Dict[str, List[str]]]] = None
|
||||
|
|
@ -375,6 +423,7 @@ class Components(BaseModel):
|
|||
links: Optional[Dict[str, Union[Link, Reference]]] = None
|
||||
# Using Any for Specification Extensions
|
||||
callbacks: Optional[Dict[str, Union[Dict[str, PathItem], Reference, Any]]] = None
|
||||
pathItems: Optional[Dict[str, Union[PathItem, Reference]]] = None
|
||||
|
||||
class Config:
|
||||
extra = "allow"
|
||||
|
|
@ -392,9 +441,11 @@ class Tag(BaseModel):
|
|||
class OpenAPI(BaseModel):
|
||||
openapi: str
|
||||
info: Info
|
||||
jsonSchemaDialect: Optional[str] = None
|
||||
servers: Optional[List[Server]] = None
|
||||
# Using Any for Specification Extensions
|
||||
paths: Dict[str, Union[PathItem, Any]]
|
||||
paths: Optional[Dict[str, Union[PathItem, Any]]] = None
|
||||
webhooks: Optional[Dict[str, Union[PathItem, Reference]]] = None
|
||||
components: Optional[Components] = None
|
||||
security: Optional[List[Dict[str, List[str]]]] = None
|
||||
tags: Optional[List[Tag]] = None
|
||||
|
|
|
|||
|
|
@ -106,9 +106,7 @@ def get_openapi_operation_parameters(
|
|||
}
|
||||
if field_info.description:
|
||||
parameter["description"] = field_info.description
|
||||
if field_info.examples:
|
||||
parameter["examples"] = jsonable_encoder(field_info.examples)
|
||||
elif field_info.example != Undefined:
|
||||
if field_info.example != Undefined:
|
||||
parameter["example"] = jsonable_encoder(field_info.example)
|
||||
if field_info.deprecated:
|
||||
parameter["deprecated"] = field_info.deprecated
|
||||
|
|
@ -134,9 +132,7 @@ def get_openapi_operation_request_body(
|
|||
if required:
|
||||
request_body_oai["required"] = required
|
||||
request_media_content: Dict[str, Any] = {"schema": body_schema}
|
||||
if field_info.examples:
|
||||
request_media_content["examples"] = jsonable_encoder(field_info.examples)
|
||||
elif field_info.example != Undefined:
|
||||
if field_info.example != Undefined:
|
||||
request_media_content["example"] = jsonable_encoder(field_info.example)
|
||||
request_body_oai["content"] = {request_media_type: request_media_content}
|
||||
return request_body_oai
|
||||
|
|
@ -392,9 +388,11 @@ def get_openapi(
|
|||
*,
|
||||
title: str,
|
||||
version: str,
|
||||
openapi_version: str = "3.0.2",
|
||||
openapi_version: str = "3.1.0",
|
||||
summary: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
routes: Sequence[BaseRoute],
|
||||
webhooks: Optional[Sequence[BaseRoute]] = None,
|
||||
tags: Optional[List[Dict[str, Any]]] = None,
|
||||
servers: Optional[List[Dict[str, Union[str, Any]]]] = None,
|
||||
terms_of_service: Optional[str] = None,
|
||||
|
|
@ -402,6 +400,8 @@ def get_openapi(
|
|||
license_info: Optional[Dict[str, Union[str, Any]]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
info: Dict[str, Any] = {"title": title, "version": version}
|
||||
if summary:
|
||||
info["summary"] = summary
|
||||
if description:
|
||||
info["description"] = description
|
||||
if terms_of_service:
|
||||
|
|
@ -415,13 +415,14 @@ def get_openapi(
|
|||
output["servers"] = servers
|
||||
components: Dict[str, Dict[str, Any]] = {}
|
||||
paths: Dict[str, Dict[str, Any]] = {}
|
||||
webhook_paths: Dict[str, Dict[str, Any]] = {}
|
||||
operation_ids: Set[str] = set()
|
||||
flat_models = get_flat_models_from_routes(routes)
|
||||
flat_models = get_flat_models_from_routes(list(routes or []) + list(webhooks or []))
|
||||
model_name_map = get_model_name_map(flat_models)
|
||||
definitions = get_model_definitions(
|
||||
flat_models=flat_models, model_name_map=model_name_map
|
||||
)
|
||||
for route in routes:
|
||||
for route in routes or []:
|
||||
if isinstance(route, routing.APIRoute):
|
||||
result = get_openapi_path(
|
||||
route=route, model_name_map=model_name_map, operation_ids=operation_ids
|
||||
|
|
@ -436,11 +437,30 @@ def get_openapi(
|
|||
)
|
||||
if path_definitions:
|
||||
definitions.update(path_definitions)
|
||||
for webhook in webhooks or []:
|
||||
if isinstance(webhook, routing.APIRoute):
|
||||
result = get_openapi_path(
|
||||
route=webhook,
|
||||
model_name_map=model_name_map,
|
||||
operation_ids=operation_ids,
|
||||
)
|
||||
if result:
|
||||
path, security_schemes, path_definitions = result
|
||||
if path:
|
||||
webhook_paths.setdefault(webhook.path_format, {}).update(path)
|
||||
if security_schemes:
|
||||
components.setdefault("securitySchemes", {}).update(
|
||||
security_schemes
|
||||
)
|
||||
if path_definitions:
|
||||
definitions.update(path_definitions)
|
||||
if definitions:
|
||||
components["schemas"] = {k: definitions[k] for k in sorted(definitions)}
|
||||
if components:
|
||||
output["components"] = components
|
||||
output["paths"] = paths
|
||||
if webhook_paths:
|
||||
output["webhooks"] = webhook_paths
|
||||
if tags:
|
||||
output["tags"] = tags
|
||||
return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True) # type: ignore
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
from typing import Any, Callable, Dict, Optional, Sequence
|
||||
from typing import Any, Callable, List, Optional, Sequence
|
||||
|
||||
from fastapi import params
|
||||
from pydantic.fields import Undefined
|
||||
from typing_extensions import Annotated, deprecated
|
||||
|
||||
|
||||
def Path( # noqa: N802
|
||||
|
|
@ -17,8 +18,14 @@ def Path( # noqa: N802
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
deprecated: Optional[bool] = None,
|
||||
include_in_schema: bool = True,
|
||||
**extra: Any,
|
||||
|
|
@ -56,8 +63,14 @@ def Query( # noqa: N802
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
deprecated: Optional[bool] = None,
|
||||
include_in_schema: bool = True,
|
||||
**extra: Any,
|
||||
|
|
@ -96,8 +109,14 @@ def Header( # noqa: N802
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
deprecated: Optional[bool] = None,
|
||||
include_in_schema: bool = True,
|
||||
**extra: Any,
|
||||
|
|
@ -136,8 +155,14 @@ def Cookie( # noqa: N802
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
deprecated: Optional[bool] = None,
|
||||
include_in_schema: bool = True,
|
||||
**extra: Any,
|
||||
|
|
@ -177,8 +202,14 @@ def Body( # noqa: N802
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
**extra: Any,
|
||||
) -> Any:
|
||||
return params.Body(
|
||||
|
|
@ -215,8 +246,14 @@ def Form( # noqa: N802
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
**extra: Any,
|
||||
) -> Any:
|
||||
return params.Form(
|
||||
|
|
@ -252,8 +289,14 @@ def File( # noqa: N802
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
**extra: Any,
|
||||
) -> Any:
|
||||
return params.File(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import warnings
|
||||
from enum import Enum
|
||||
from typing import Any, Callable, Dict, Optional, Sequence
|
||||
from typing import Any, Callable, List, Optional, Sequence
|
||||
|
||||
from pydantic.fields import FieldInfo, Undefined
|
||||
from typing_extensions import Annotated, deprecated
|
||||
|
||||
|
||||
class ParamTypes(Enum):
|
||||
|
|
@ -28,16 +30,30 @@ class Param(FieldInfo):
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
deprecated: Optional[bool] = None,
|
||||
include_in_schema: bool = True,
|
||||
**extra: Any,
|
||||
):
|
||||
self.deprecated = deprecated
|
||||
if example is not Undefined:
|
||||
warnings.warn(
|
||||
"`example` has been depreacated, please use `examples` instead",
|
||||
category=DeprecationWarning,
|
||||
stacklevel=1,
|
||||
)
|
||||
self.example = example
|
||||
self.examples = examples
|
||||
self.include_in_schema = include_in_schema
|
||||
extra_kwargs = {**extra}
|
||||
if examples:
|
||||
extra_kwargs["examples"] = examples
|
||||
super().__init__(
|
||||
default=default,
|
||||
alias=alias,
|
||||
|
|
@ -50,7 +66,7 @@ class Param(FieldInfo):
|
|||
min_length=min_length,
|
||||
max_length=max_length,
|
||||
regex=regex,
|
||||
**extra,
|
||||
**extra_kwargs,
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
|
@ -74,8 +90,14 @@ class Path(Param):
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
deprecated: Optional[bool] = None,
|
||||
include_in_schema: bool = True,
|
||||
**extra: Any,
|
||||
|
|
@ -119,8 +141,14 @@ class Query(Param):
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
deprecated: Optional[bool] = None,
|
||||
include_in_schema: bool = True,
|
||||
**extra: Any,
|
||||
|
|
@ -163,8 +191,14 @@ class Header(Param):
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
deprecated: Optional[bool] = None,
|
||||
include_in_schema: bool = True,
|
||||
**extra: Any,
|
||||
|
|
@ -207,8 +241,14 @@ class Cookie(Param):
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
deprecated: Optional[bool] = None,
|
||||
include_in_schema: bool = True,
|
||||
**extra: Any,
|
||||
|
|
@ -250,14 +290,28 @@ class Body(FieldInfo):
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
**extra: Any,
|
||||
):
|
||||
self.embed = embed
|
||||
self.media_type = media_type
|
||||
if example is not Undefined:
|
||||
warnings.warn(
|
||||
"`example` has been depreacated, please use `examples` instead",
|
||||
category=DeprecationWarning,
|
||||
stacklevel=1,
|
||||
)
|
||||
self.example = example
|
||||
self.examples = examples
|
||||
extra_kwargs = {**extra}
|
||||
if examples is not None:
|
||||
extra_kwargs["examples"] = examples
|
||||
super().__init__(
|
||||
default=default,
|
||||
alias=alias,
|
||||
|
|
@ -270,7 +324,7 @@ class Body(FieldInfo):
|
|||
min_length=min_length,
|
||||
max_length=max_length,
|
||||
regex=regex,
|
||||
**extra,
|
||||
**extra_kwargs,
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
|
@ -293,8 +347,14 @@ class Form(Body):
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
**extra: Any,
|
||||
):
|
||||
super().__init__(
|
||||
|
|
@ -333,8 +393,14 @@ class File(Form):
|
|||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
regex: Optional[str] = None,
|
||||
example: Any = Undefined,
|
||||
examples: Optional[Dict[str, Any]] = None,
|
||||
examples: Optional[List[Any]] = None,
|
||||
example: Annotated[
|
||||
Optional[Any],
|
||||
deprecated(
|
||||
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
|
||||
"although still supported. Use examples instead."
|
||||
),
|
||||
] = Undefined,
|
||||
**extra: Any,
|
||||
):
|
||||
super().__init__(
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ classifiers = [
|
|||
dependencies = [
|
||||
"starlette>=0.27.0,<0.28.0",
|
||||
"pydantic>=1.7.4,!=1.8,!=1.8.1,<2.0.0",
|
||||
"typing-extensions>=4.5.0"
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/foo": {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ async def a():
|
|||
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/a": {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/a/{id}": {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/a/{id}": {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/a": {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/a": {
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/default": {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/api_route": {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/a/": {
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/with-duplicates": {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ def test_openapi():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/app": {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ expected_schema = {
|
|||
}
|
||||
},
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
"/foo": {
|
||||
"get": {
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/model/{name}": {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ def test_top_level_generate_unique_id():
|
|||
response = client.get("/openapi.json")
|
||||
data = response.json()
|
||||
assert data == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
|
|
@ -249,7 +249,7 @@ def test_router_overrides_generate_unique_id():
|
|||
response = client.get("/openapi.json")
|
||||
data = response.json()
|
||||
assert data == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
|
|
@ -450,7 +450,7 @@ def test_router_include_overrides_generate_unique_id():
|
|||
response = client.get("/openapi.json")
|
||||
data = response.json()
|
||||
assert data == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
|
|
@ -661,7 +661,7 @@ def test_subrouter_top_level_include_overrides_generate_unique_id():
|
|||
response = client.get("/openapi.json")
|
||||
data = response.json()
|
||||
assert data == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
|
|
@ -928,7 +928,7 @@ def test_router_path_operation_overrides_generate_unique_id():
|
|||
response = client.get("/openapi.json")
|
||||
data = response.json()
|
||||
assert data == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
|
|
@ -1136,7 +1136,7 @@ def test_app_path_operation_overrides_generate_unique_id():
|
|||
response = client.get("/openapi.json")
|
||||
data = response.json()
|
||||
assert data == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
|
|
@ -1353,7 +1353,7 @@ def test_callback_override_generate_unique_id():
|
|||
response = client.get("/openapi.json")
|
||||
data = response.json()
|
||||
assert data == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/product": {
|
||||
|
|
|
|||
|
|
@ -443,7 +443,7 @@ def test_openapi():
|
|||
assert issubclass(w[-1].category, UserWarning)
|
||||
assert "Duplicate Operation ID" in str(w[-1].message)
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/override1": {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/a/compute": {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ def test_openapi():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ def test_openapi():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"servers": [
|
||||
{"url": "/", "description": "Default, relative server"},
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
data = response.json()
|
||||
assert data == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/{user_id}": {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ async def hidden_query(
|
|||
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/hidden_cookie": {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ schema = {
|
|||
}
|
||||
},
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
"/": {
|
||||
"get": {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ def test_openapi_schema():
|
|||
}
|
||||
},
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
"/{repeated_alias}": {
|
||||
"get": {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/{id}": {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ def test_openapi_schema():
|
|||
assert response.status_code == 200, response.text
|
||||
# insert_assert(response.json())
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/products": {
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/dict": {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/a": {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/a": {
|
||||
|
|
|
|||
|
|
@ -507,7 +507,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/no_response_model-no_annotation-return_model": {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/valid1": {
|
||||
|
|
|
|||
|
|
@ -1,240 +1,220 @@
|
|||
from typing import Union
|
||||
|
||||
import pytest
|
||||
from fastapi import Body, Cookie, FastAPI, Header, Path, Query
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
def create_app():
|
||||
app = FastAPI()
|
||||
|
||||
class Item(BaseModel):
|
||||
data: str
|
||||
class Item(BaseModel):
|
||||
data: str
|
||||
|
||||
class Config:
|
||||
schema_extra = {"example": {"data": "Data in schema_extra"}}
|
||||
class Config:
|
||||
schema_extra = {"example": {"data": "Data in schema_extra"}}
|
||||
|
||||
@app.post("/schema_extra/")
|
||||
def schema_extra(item: Item):
|
||||
return item
|
||||
|
||||
@app.post("/schema_extra/")
|
||||
def schema_extra(item: Item):
|
||||
return item
|
||||
with pytest.warns(DeprecationWarning):
|
||||
|
||||
@app.post("/example/")
|
||||
def example(item: Item = Body(example={"data": "Data in Body example"})):
|
||||
return item
|
||||
|
||||
@app.post("/example/")
|
||||
def example(item: Item = Body(example={"data": "Data in Body example"})):
|
||||
return item
|
||||
@app.post("/examples/")
|
||||
def examples(
|
||||
item: Item = Body(
|
||||
examples=[
|
||||
{"data": "Data in Body examples, example1"},
|
||||
{"data": "Data in Body examples, example2"},
|
||||
],
|
||||
)
|
||||
):
|
||||
return item
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
|
||||
@app.post("/examples/")
|
||||
def examples(
|
||||
item: Item = Body(
|
||||
examples={
|
||||
"example1": {
|
||||
"summary": "example1 summary",
|
||||
"value": {"data": "Data in Body examples, example1"},
|
||||
},
|
||||
"example2": {"value": {"data": "Data in Body examples, example2"}},
|
||||
},
|
||||
)
|
||||
):
|
||||
return item
|
||||
@app.post("/example_examples/")
|
||||
def example_examples(
|
||||
item: Item = Body(
|
||||
example={"data": "Overridden example"},
|
||||
examples=[
|
||||
{"data": "examples example_examples 1"},
|
||||
{"data": "examples example_examples 2"},
|
||||
],
|
||||
)
|
||||
):
|
||||
return item
|
||||
|
||||
# TODO: enable these tests once/if Form(embed=False) is supported
|
||||
# TODO: In that case, define if File() should support example/examples too
|
||||
# @app.post("/form_example")
|
||||
# def form_example(firstname: str = Form(example="John")):
|
||||
# return firstname
|
||||
|
||||
@app.post("/example_examples/")
|
||||
def example_examples(
|
||||
item: Item = Body(
|
||||
example={"data": "Overridden example"},
|
||||
examples={
|
||||
"example1": {"value": {"data": "examples example_examples 1"}},
|
||||
"example2": {"value": {"data": "examples example_examples 2"}},
|
||||
},
|
||||
)
|
||||
):
|
||||
return item
|
||||
# @app.post("/form_examples")
|
||||
# def form_examples(
|
||||
# lastname: str = Form(
|
||||
# ...,
|
||||
# examples={
|
||||
# "example1": {"summary": "last name summary", "value": "Doe"},
|
||||
# "example2": {"value": "Doesn't"},
|
||||
# },
|
||||
# ),
|
||||
# ):
|
||||
# return lastname
|
||||
|
||||
# @app.post("/form_example_examples")
|
||||
# def form_example_examples(
|
||||
# lastname: str = Form(
|
||||
# ...,
|
||||
# example="Doe overridden",
|
||||
# examples={
|
||||
# "example1": {"summary": "last name summary", "value": "Doe"},
|
||||
# "example2": {"value": "Doesn't"},
|
||||
# },
|
||||
# ),
|
||||
# ):
|
||||
# return lastname
|
||||
|
||||
# TODO: enable these tests once/if Form(embed=False) is supported
|
||||
# TODO: In that case, define if File() should support example/examples too
|
||||
# @app.post("/form_example")
|
||||
# def form_example(firstname: str = Form(example="John")):
|
||||
# return firstname
|
||||
with pytest.warns(DeprecationWarning):
|
||||
|
||||
@app.get("/path_example/{item_id}")
|
||||
def path_example(
|
||||
item_id: str = Path(
|
||||
example="item_1",
|
||||
),
|
||||
):
|
||||
return item_id
|
||||
|
||||
# @app.post("/form_examples")
|
||||
# def form_examples(
|
||||
# lastname: str = Form(
|
||||
# ...,
|
||||
# examples={
|
||||
# "example1": {"summary": "last name summary", "value": "Doe"},
|
||||
# "example2": {"value": "Doesn't"},
|
||||
# },
|
||||
# ),
|
||||
# ):
|
||||
# return lastname
|
||||
@app.get("/path_examples/{item_id}")
|
||||
def path_examples(
|
||||
item_id: str = Path(
|
||||
examples=["item_1", "item_2"],
|
||||
),
|
||||
):
|
||||
return item_id
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
|
||||
# @app.post("/form_example_examples")
|
||||
# def form_example_examples(
|
||||
# lastname: str = Form(
|
||||
# ...,
|
||||
# example="Doe overridden",
|
||||
# examples={
|
||||
# "example1": {"summary": "last name summary", "value": "Doe"},
|
||||
# "example2": {"value": "Doesn't"},
|
||||
# },
|
||||
# ),
|
||||
# ):
|
||||
# return lastname
|
||||
@app.get("/path_example_examples/{item_id}")
|
||||
def path_example_examples(
|
||||
item_id: str = Path(
|
||||
example="item_overridden",
|
||||
examples=["item_1", "item_2"],
|
||||
),
|
||||
):
|
||||
return item_id
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
|
||||
@app.get("/path_example/{item_id}")
|
||||
def path_example(
|
||||
item_id: str = Path(
|
||||
example="item_1",
|
||||
),
|
||||
):
|
||||
return item_id
|
||||
@app.get("/query_example/")
|
||||
def query_example(
|
||||
data: Union[str, None] = Query(
|
||||
default=None,
|
||||
example="query1",
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
@app.get("/query_examples/")
|
||||
def query_examples(
|
||||
data: Union[str, None] = Query(
|
||||
default=None,
|
||||
examples=["query1", "query2"],
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
@app.get("/path_examples/{item_id}")
|
||||
def path_examples(
|
||||
item_id: str = Path(
|
||||
examples={
|
||||
"example1": {"summary": "item ID summary", "value": "item_1"},
|
||||
"example2": {"value": "item_2"},
|
||||
},
|
||||
),
|
||||
):
|
||||
return item_id
|
||||
with pytest.warns(DeprecationWarning):
|
||||
|
||||
@app.get("/query_example_examples/")
|
||||
def query_example_examples(
|
||||
data: Union[str, None] = Query(
|
||||
default=None,
|
||||
example="query_overridden",
|
||||
examples=["query1", "query2"],
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
@app.get("/path_example_examples/{item_id}")
|
||||
def path_example_examples(
|
||||
item_id: str = Path(
|
||||
example="item_overridden",
|
||||
examples={
|
||||
"example1": {"summary": "item ID summary", "value": "item_1"},
|
||||
"example2": {"value": "item_2"},
|
||||
},
|
||||
),
|
||||
):
|
||||
return item_id
|
||||
with pytest.warns(DeprecationWarning):
|
||||
|
||||
@app.get("/header_example/")
|
||||
def header_example(
|
||||
data: Union[str, None] = Header(
|
||||
default=None,
|
||||
example="header1",
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
@app.get("/query_example/")
|
||||
def query_example(
|
||||
data: Union[str, None] = Query(
|
||||
default=None,
|
||||
example="query1",
|
||||
),
|
||||
):
|
||||
return data
|
||||
@app.get("/header_examples/")
|
||||
def header_examples(
|
||||
data: Union[str, None] = Header(
|
||||
default=None,
|
||||
examples=[
|
||||
"header1",
|
||||
"header2",
|
||||
],
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
|
||||
@app.get("/query_examples/")
|
||||
def query_examples(
|
||||
data: Union[str, None] = Query(
|
||||
default=None,
|
||||
examples={
|
||||
"example1": {"summary": "Query example 1", "value": "query1"},
|
||||
"example2": {"value": "query2"},
|
||||
},
|
||||
),
|
||||
):
|
||||
return data
|
||||
@app.get("/header_example_examples/")
|
||||
def header_example_examples(
|
||||
data: Union[str, None] = Header(
|
||||
default=None,
|
||||
example="header_overridden",
|
||||
examples=["header1", "header2"],
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
|
||||
@app.get("/query_example_examples/")
|
||||
def query_example_examples(
|
||||
data: Union[str, None] = Query(
|
||||
default=None,
|
||||
example="query_overridden",
|
||||
examples={
|
||||
"example1": {"summary": "Query example 1", "value": "query1"},
|
||||
"example2": {"value": "query2"},
|
||||
},
|
||||
),
|
||||
):
|
||||
return data
|
||||
@app.get("/cookie_example/")
|
||||
def cookie_example(
|
||||
data: Union[str, None] = Cookie(
|
||||
default=None,
|
||||
example="cookie1",
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
@app.get("/cookie_examples/")
|
||||
def cookie_examples(
|
||||
data: Union[str, None] = Cookie(
|
||||
default=None,
|
||||
examples=["cookie1", "cookie2"],
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
@app.get("/header_example/")
|
||||
def header_example(
|
||||
data: Union[str, None] = Header(
|
||||
default=None,
|
||||
example="header1",
|
||||
),
|
||||
):
|
||||
return data
|
||||
with pytest.warns(DeprecationWarning):
|
||||
|
||||
@app.get("/cookie_example_examples/")
|
||||
def cookie_example_examples(
|
||||
data: Union[str, None] = Cookie(
|
||||
default=None,
|
||||
example="cookie_overridden",
|
||||
examples=["cookie1", "cookie2"],
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
@app.get("/header_examples/")
|
||||
def header_examples(
|
||||
data: Union[str, None] = Header(
|
||||
default=None,
|
||||
examples={
|
||||
"example1": {"summary": "header example 1", "value": "header1"},
|
||||
"example2": {"value": "header2"},
|
||||
},
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
|
||||
@app.get("/header_example_examples/")
|
||||
def header_example_examples(
|
||||
data: Union[str, None] = Header(
|
||||
default=None,
|
||||
example="header_overridden",
|
||||
examples={
|
||||
"example1": {"summary": "Query example 1", "value": "header1"},
|
||||
"example2": {"value": "header2"},
|
||||
},
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
|
||||
@app.get("/cookie_example/")
|
||||
def cookie_example(
|
||||
data: Union[str, None] = Cookie(
|
||||
default=None,
|
||||
example="cookie1",
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
|
||||
@app.get("/cookie_examples/")
|
||||
def cookie_examples(
|
||||
data: Union[str, None] = Cookie(
|
||||
default=None,
|
||||
examples={
|
||||
"example1": {"summary": "cookie example 1", "value": "cookie1"},
|
||||
"example2": {"value": "cookie2"},
|
||||
},
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
|
||||
@app.get("/cookie_example_examples/")
|
||||
def cookie_example_examples(
|
||||
data: Union[str, None] = Cookie(
|
||||
default=None,
|
||||
example="cookie_overridden",
|
||||
examples={
|
||||
"example1": {"summary": "Query example 1", "value": "cookie1"},
|
||||
"example2": {"value": "cookie2"},
|
||||
},
|
||||
),
|
||||
):
|
||||
return data
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
return app
|
||||
|
||||
|
||||
def test_call_api():
|
||||
app = create_app()
|
||||
client = TestClient(app)
|
||||
response = client.post("/schema_extra/", json={"data": "Foo"})
|
||||
assert response.status_code == 200, response.text
|
||||
response = client.post("/example/", json={"data": "Foo"})
|
||||
|
|
@ -277,10 +257,12 @@ def test_openapi_schema():
|
|||
* Body(example={}) overrides schema_extra in pydantic model
|
||||
* Body(examples{}) overrides Body(example={}) and schema_extra in pydantic model
|
||||
"""
|
||||
app = create_app()
|
||||
client = TestClient(app)
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/schema_extra/": {
|
||||
|
|
@ -351,20 +333,14 @@ def test_openapi_schema():
|
|||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"summary": "example1 summary",
|
||||
"value": {
|
||||
"data": "Data in Body examples, example1"
|
||||
},
|
||||
},
|
||||
"example2": {
|
||||
"value": {
|
||||
"data": "Data in Body examples, example2"
|
||||
}
|
||||
},
|
||||
},
|
||||
"schema": {
|
||||
"allOf": [{"$ref": "#/components/schemas/Item"}],
|
||||
"title": "Item",
|
||||
"examples": [
|
||||
{"data": "Data in Body examples, example1"},
|
||||
{"data": "Data in Body examples, example2"},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
|
|
@ -394,15 +370,15 @@ def test_openapi_schema():
|
|||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Item"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"value": {"data": "examples example_examples 1"}
|
||||
},
|
||||
"example2": {
|
||||
"value": {"data": "examples example_examples 2"}
|
||||
},
|
||||
"schema": {
|
||||
"allOf": [{"$ref": "#/components/schemas/Item"}],
|
||||
"title": "Item",
|
||||
"examples": [
|
||||
{"data": "examples example_examples 1"},
|
||||
{"data": "examples example_examples 2"},
|
||||
],
|
||||
},
|
||||
"example": {"data": "Overridden example"},
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
|
|
@ -463,13 +439,10 @@ def test_openapi_schema():
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"summary": "item ID summary",
|
||||
"value": "item_1",
|
||||
},
|
||||
"example2": {"value": "item_2"},
|
||||
"schema": {
|
||||
"title": "Item Id",
|
||||
"type": "string",
|
||||
"examples": ["item_1", "item_2"],
|
||||
},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
|
|
@ -500,14 +473,12 @@ def test_openapi_schema():
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"summary": "item ID summary",
|
||||
"value": "item_1",
|
||||
},
|
||||
"example2": {"value": "item_2"},
|
||||
"schema": {
|
||||
"title": "Item Id",
|
||||
"type": "string",
|
||||
"examples": ["item_1", "item_2"],
|
||||
},
|
||||
"example": "item_overridden",
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -568,13 +539,10 @@ def test_openapi_schema():
|
|||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Data", "type": "string"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"summary": "Query example 1",
|
||||
"value": "query1",
|
||||
},
|
||||
"example2": {"value": "query2"},
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Data",
|
||||
"examples": ["query1", "query2"],
|
||||
},
|
||||
"name": "data",
|
||||
"in": "query",
|
||||
|
|
@ -605,14 +573,12 @@ def test_openapi_schema():
|
|||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Data", "type": "string"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"summary": "Query example 1",
|
||||
"value": "query1",
|
||||
},
|
||||
"example2": {"value": "query2"},
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Data",
|
||||
"examples": ["query1", "query2"],
|
||||
},
|
||||
"example": "query_overridden",
|
||||
"name": "data",
|
||||
"in": "query",
|
||||
}
|
||||
|
|
@ -642,7 +608,7 @@ def test_openapi_schema():
|
|||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Data", "type": "string"},
|
||||
"schema": {"type": "string", "title": "Data"},
|
||||
"example": "header1",
|
||||
"name": "data",
|
||||
"in": "header",
|
||||
|
|
@ -673,13 +639,10 @@ def test_openapi_schema():
|
|||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Data", "type": "string"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"summary": "header example 1",
|
||||
"value": "header1",
|
||||
},
|
||||
"example2": {"value": "header2"},
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Data",
|
||||
"examples": ["header1", "header2"],
|
||||
},
|
||||
"name": "data",
|
||||
"in": "header",
|
||||
|
|
@ -710,14 +673,12 @@ def test_openapi_schema():
|
|||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Data", "type": "string"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"summary": "Query example 1",
|
||||
"value": "header1",
|
||||
},
|
||||
"example2": {"value": "header2"},
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Data",
|
||||
"examples": ["header1", "header2"],
|
||||
},
|
||||
"example": "header_overridden",
|
||||
"name": "data",
|
||||
"in": "header",
|
||||
}
|
||||
|
|
@ -747,7 +708,7 @@ def test_openapi_schema():
|
|||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Data", "type": "string"},
|
||||
"schema": {"type": "string", "title": "Data"},
|
||||
"example": "cookie1",
|
||||
"name": "data",
|
||||
"in": "cookie",
|
||||
|
|
@ -778,13 +739,10 @@ def test_openapi_schema():
|
|||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Data", "type": "string"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"summary": "cookie example 1",
|
||||
"value": "cookie1",
|
||||
},
|
||||
"example2": {"value": "cookie2"},
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Data",
|
||||
"examples": ["cookie1", "cookie2"],
|
||||
},
|
||||
"name": "data",
|
||||
"in": "cookie",
|
||||
|
|
@ -815,14 +773,12 @@ def test_openapi_schema():
|
|||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Data", "type": "string"},
|
||||
"examples": {
|
||||
"example1": {
|
||||
"summary": "Query example 1",
|
||||
"value": "cookie1",
|
||||
},
|
||||
"example2": {"value": "cookie2"},
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Data",
|
||||
"examples": ["cookie1", "cookie2"],
|
||||
},
|
||||
"example": "cookie_overridden",
|
||||
"name": "data",
|
||||
"in": "cookie",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/users/me": {
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/login": {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ def test_openapi_schema():
|
|||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue