mirror of https://github.com/tiangolo/fastapi.git
✨ Add support for Pydantic v1 and above 🎉 (#646)
* Make compatible with pydantic v1 * Remove unused import * Remove unused ignores * Update pydantic version * Fix minor formatting issue * ⏪ Revert removing iterate_in_threadpool * ✨ Add backwards compatibility with Pydantic 0.32.2 with deprecation warnings * ✅ Update tests to not break when using Pydantic < 1.0.0 * 📝 Update docs for Pydantic version 1.0.0 * 📌 Update Pydantic range version to support from 0.32.2 * 🎨 Format test imports * ✨ Add support for Pydantic < 1.2 for populate_validators * ✨ Add backwards compatibility for Pydantic < 1.2.0 with required fields * 📌 Relax requirement for Pydantic to < 2.0.0 🎉 🚀 * 💚 Update pragma coverage for older Pydantic versions
This commit is contained in:
parent
90a5796b94
commit
ab2b86fe2c
2
Pipfile
2
Pipfile
|
|
@ -26,7 +26,7 @@ uvicorn = "*"
|
|||
|
||||
[packages]
|
||||
starlette = "==0.12.9"
|
||||
pydantic = "==0.32.2"
|
||||
pydantic = "==1.0.0"
|
||||
databases = {extras = ["sqlite"],version = "*"}
|
||||
hypercorn = "*"
|
||||
orjson = "*"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
from fastapi import Body, FastAPI
|
||||
from pydantic import BaseModel, Schema
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str = Schema(None, title="The description of the item", max_length=300)
|
||||
price: float = Schema(..., gt=0, description="The price must be greater than zero")
|
||||
description: str = Field(None, title="The description of the item", max_length=300)
|
||||
price: float = Field(..., gt=0, description="The price must be greater than zero")
|
||||
tax: float = None
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ async def read_item(item_id: str):
|
|||
async def update_item(item_id: str, item: Item):
|
||||
stored_item_data = items[item_id]
|
||||
stored_item_model = Item(**stored_item_data)
|
||||
update_data = item.dict(skip_defaults=True)
|
||||
update_data = item.dict(exclude_unset=True)
|
||||
updated_item = stored_item_model.copy(update=update_data)
|
||||
items[item_id] = jsonable_encoder(updated_item)
|
||||
return updated_item
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import EmailStr
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import EmailStr
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import EmailStr
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from pydantic.types import EmailStr
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,6 @@ items = {
|
|||
}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}", response_model=Item, response_model_skip_defaults=True)
|
||||
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
|
||||
async def read_item(item_id: str):
|
||||
return items[item_id]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
The same way you can declare additional validation and metadata in path operation function parameters with `Query`, `Path` and `Body`, you can declare validation and metadata inside of Pydantic models using `Schema`.
|
||||
The same way you can declare additional validation and metadata in path operation function parameters with `Query`, `Path` and `Body`, you can declare validation and metadata inside of Pydantic models using Pydantic's `Field`.
|
||||
|
||||
## Import Schema
|
||||
## Import `Field`
|
||||
|
||||
First, you have to import it:
|
||||
|
||||
|
|
@ -9,32 +9,34 @@ First, you have to import it:
|
|||
```
|
||||
|
||||
!!! warning
|
||||
Notice that `Schema` is imported directly from `pydantic`, not from `fastapi` as are all the rest (`Query`, `Path`, `Body`, etc).
|
||||
Notice that `Field` is imported directly from `pydantic`, not from `fastapi` as are all the rest (`Query`, `Path`, `Body`, etc).
|
||||
|
||||
|
||||
## Declare model attributes
|
||||
|
||||
You can then use `Schema` with model attributes:
|
||||
You can then use `Field` with model attributes:
|
||||
|
||||
```Python hl_lines="9 10"
|
||||
{!./src/body_schema/tutorial001.py!}
|
||||
```
|
||||
|
||||
`Schema` works the same way as `Query`, `Path` and `Body`, it has all the same parameters, etc.
|
||||
`Field` works the same way as `Query`, `Path` and `Body`, it has all the same parameters, etc.
|
||||
|
||||
!!! note "Technical Details"
|
||||
Actually, `Query`, `Path` and others you'll see next are subclasses of a common `Param` which is itself a subclass of Pydantic's `Schema`.
|
||||
Actually, `Query`, `Path` and others you'll see next create objects of subclasses of a common `Param` class, which is itself a subclass of Pydantic's `FieldInfo` class.
|
||||
|
||||
`Body` is also a subclass of `Schema` directly. And there are others you will see later that are subclasses of `Body`.
|
||||
And Pydantic's `Field` returns an instance of `FieldInfo` as well.
|
||||
|
||||
But remember that when you import `Query`, `Path` and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
`Body` also returns objects of a subclass of `FieldInfo` directly. And there are others you will see later that are subclasses of the `Body` class.
|
||||
|
||||
Remember that when you import `Query`, `Path`, and others from `fastapi`, <a href="https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#recap" target="_blank">those are actually functions that return classes of the same name</a>.
|
||||
|
||||
!!! tip
|
||||
Notice how each model's attribute with a type, default value and `Schema` has the same structure as a path operation function's parameter, with `Schema` instead of `Path`, `Query` and `Body`.
|
||||
Notice how each model's attribute with a type, default value and `Field` has the same structure as a path operation function's parameter, with `Field` instead of `Path`, `Query` and `Body`.
|
||||
|
||||
## Schema extras
|
||||
|
||||
In `Schema`, `Path`, `Query`, `Body` and others you'll see later, you can declare extra parameters apart from those described before.
|
||||
In `Field`, `Path`, `Query`, `Body` and others you'll see later, you can declare extra parameters apart from those described before.
|
||||
|
||||
Those parameters will be added as-is to the output JSON Schema.
|
||||
|
||||
|
|
@ -55,6 +57,6 @@ And it would look in the `/docs` like this:
|
|||
|
||||
## Recap
|
||||
|
||||
You can use Pydantic's `Schema` to declare extra validations and metadata for model attributes.
|
||||
You can use Pydantic's `Field` to declare extra validations and metadata for model attributes.
|
||||
|
||||
You can also use the extra keyword arguments to pass additional JSON Schema metadata.
|
||||
|
|
@ -41,15 +41,15 @@ This means that you can send only the data that you want to update, leaving the
|
|||
|
||||
But this guide shows you, more or less, how they are intended to be used.
|
||||
|
||||
### Using Pydantic's `skip_defaults` parameter
|
||||
### Using Pydantic's `exclude_unset` parameter
|
||||
|
||||
If you want to receive partial updates, it's very useful to use the parameter `skip_defaults` in Pydantic's model's `.dict()`.
|
||||
If you want to receive partial updates, it's very useful to use the parameter `exclude_unset` in Pydantic's model's `.dict()`.
|
||||
|
||||
Like `item.dict(skip_defaults=True)`.
|
||||
Like `item.dict(exclude_unset=True)`.
|
||||
|
||||
That would generate a `dict` with only the data that was set when creating the `item` model, excluding default values.
|
||||
|
||||
Then you can use this to generate a `dict` with only the data that was set, omitting default values:
|
||||
Then you can use this to generate a `dict` with only the data that was set (sent in the request), omitting default values:
|
||||
|
||||
```Python hl_lines="34"
|
||||
{!./src/body_updates/tutorial002.py!}
|
||||
|
|
@ -72,7 +72,7 @@ In summary, to apply partial updates you would:
|
|||
* (Optionally) use `PATCH` instead of `PUT`.
|
||||
* Retrieve the stored data.
|
||||
* Put that data in a Pydantic model.
|
||||
* Generate a `dict` without default values from the input model (using `skip_defaults`).
|
||||
* Generate a `dict` without default values from the input model (using `exclude_unset`).
|
||||
* This way you can update only the values actually set by the user, instead of overriding values already stored with default values in your model.
|
||||
* Create a copy of the stored model, updating it's attributes with the received partial updates (using the `update` parameter).
|
||||
* Convert the copied model to something that can be stored in your DB (for example, using the `jsonable_encoder`).
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ This is especially the case for user models, because:
|
|||
|
||||
Here's a general idea of how the models could look like with their password fields and the places where they are used:
|
||||
|
||||
```Python hl_lines="8 10 15 21 23 32 34 39 40"
|
||||
```Python hl_lines="7 9 14 20 22 27 28 31 32 33 38 39"
|
||||
{!./src/extra_models/tutorial001.py!}
|
||||
```
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ All the data conversion, validation, documentation, etc. will still work as norm
|
|||
|
||||
That way, we can declare just the differences between the models (with plaintext `password`, with `hashed_password` and without password):
|
||||
|
||||
```Python hl_lines="8 14 15 18 19 22 23"
|
||||
```Python hl_lines="7 13 14 17 18 21 22"
|
||||
{!./src/extra_models/tutorial002.py!}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -33,13 +33,13 @@ But most importantly:
|
|||
|
||||
Here we are declaring a `UserIn` model, it will contain a plaintext password:
|
||||
|
||||
```Python hl_lines="8 10"
|
||||
```Python hl_lines="7 9"
|
||||
{!./src/response_model/tutorial002.py!}
|
||||
```
|
||||
|
||||
And we are using this model to declare our input and the same model to declare our output:
|
||||
|
||||
```Python hl_lines="16 17"
|
||||
```Python hl_lines="15 16"
|
||||
{!./src/response_model/tutorial002.py!}
|
||||
```
|
||||
|
||||
|
|
@ -56,19 +56,19 @@ But if we use the same model for another path operation, we could be sending our
|
|||
|
||||
We can instead create an input model with the plaintext password and an output model without it:
|
||||
|
||||
```Python hl_lines="8 10 15"
|
||||
```Python hl_lines="7 9 14"
|
||||
{!./src/response_model/tutorial003.py!}
|
||||
```
|
||||
|
||||
Here, even though our path operation function is returning the same input user that contains the password:
|
||||
|
||||
```Python hl_lines="23"
|
||||
```Python hl_lines="22"
|
||||
{!./src/response_model/tutorial003.py!}
|
||||
```
|
||||
|
||||
...we declared the `response_model` to be our model `UserOut`, that doesn't include the password:
|
||||
|
||||
```Python hl_lines="21"
|
||||
```Python hl_lines="20"
|
||||
{!./src/response_model/tutorial003.py!}
|
||||
```
|
||||
|
||||
|
|
@ -100,15 +100,15 @@ but you might want to omit them from the result if they were not actually stored
|
|||
|
||||
For example, if you have models with many optional attributes in a NoSQL database, but you don't want to send very long JSON responses full of default values.
|
||||
|
||||
### Use the `response_model_skip_defaults` parameter
|
||||
### Use the `response_model_exclude_unset` parameter
|
||||
|
||||
You can set the *path operation decorator* parameter `response_model_skip_defaults=True`:
|
||||
You can set the *path operation decorator* parameter `response_model_exclude_unset=True`:
|
||||
|
||||
```Python hl_lines="24"
|
||||
{!./src/response_model/tutorial004.py!}
|
||||
```
|
||||
|
||||
and those default values won't be included in the response.
|
||||
and those default values won't be included in the response, only the values actually set.
|
||||
|
||||
So, if you send a request to that *path operation* for the item with ID `foo`, the response (not including default values) will be:
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ So, if you send a request to that *path operation* for the item with ID `foo`, t
|
|||
```
|
||||
|
||||
!!! info
|
||||
FastAPI uses Pydantic model's `.dict()` with <a href="https://pydantic-docs.helpmanual.io/#copying" target="_blank">its `skip_defaults` parameter</a> to achieve this.
|
||||
FastAPI uses Pydantic model's `.dict()` with <a href="https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict" target="_blank">its `exclude_unset` parameter</a> to achieve this.
|
||||
|
||||
#### Data with values for fields with defaults
|
||||
|
||||
|
|
@ -194,4 +194,4 @@ If you forget to use a `set` and use a `list` or `tuple` instead, FastAPI will s
|
|||
|
||||
Use the path operation decorator's parameter `response_model` to define response models and especially to ensure private data is filtered out.
|
||||
|
||||
Use `response_model_skip_defaults` to return only the values explicitly set.
|
||||
Use `response_model_exclude_unset` to return only the values explicitly set.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from fastapi.openapi.docs import (
|
|||
)
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
from fastapi.params import Depends
|
||||
from fastapi.utils import warning_response_model_skip_defaults_deprecated
|
||||
from starlette.applications import Starlette
|
||||
from starlette.datastructures import State
|
||||
from starlette.exceptions import ExceptionMiddleware, HTTPException
|
||||
|
|
@ -159,11 +160,14 @@ class FastAPI(Starlette):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> None:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
self.router.add_api_route(
|
||||
path,
|
||||
endpoint=endpoint,
|
||||
|
|
@ -181,7 +185,9 @@ class FastAPI(Starlette):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class or self.default_response_class,
|
||||
name=name,
|
||||
|
|
@ -205,11 +211,15 @@ class FastAPI(Starlette):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
|
||||
def decorator(func: Callable) -> Callable:
|
||||
self.router.add_api_route(
|
||||
path,
|
||||
|
|
@ -228,7 +238,9 @@ class FastAPI(Starlette):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class or self.default_response_class,
|
||||
name=name,
|
||||
|
|
@ -286,11 +298,14 @@ class FastAPI(Starlette):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.router.get(
|
||||
path,
|
||||
response_model=response_model,
|
||||
|
|
@ -306,7 +321,9 @@ class FastAPI(Starlette):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class or self.default_response_class,
|
||||
name=name,
|
||||
|
|
@ -329,11 +346,14 @@ class FastAPI(Starlette):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.router.put(
|
||||
path,
|
||||
response_model=response_model,
|
||||
|
|
@ -349,7 +369,9 @@ class FastAPI(Starlette):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class or self.default_response_class,
|
||||
name=name,
|
||||
|
|
@ -372,11 +394,14 @@ class FastAPI(Starlette):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.router.post(
|
||||
path,
|
||||
response_model=response_model,
|
||||
|
|
@ -392,7 +417,9 @@ class FastAPI(Starlette):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class or self.default_response_class,
|
||||
name=name,
|
||||
|
|
@ -415,11 +442,14 @@ class FastAPI(Starlette):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.router.delete(
|
||||
path,
|
||||
response_model=response_model,
|
||||
|
|
@ -435,7 +465,9 @@ class FastAPI(Starlette):
|
|||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
operation_id=operation_id,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class or self.default_response_class,
|
||||
name=name,
|
||||
|
|
@ -458,11 +490,14 @@ class FastAPI(Starlette):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.router.options(
|
||||
path,
|
||||
response_model=response_model,
|
||||
|
|
@ -478,7 +513,9 @@ class FastAPI(Starlette):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class or self.default_response_class,
|
||||
name=name,
|
||||
|
|
@ -501,11 +538,14 @@ class FastAPI(Starlette):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.router.head(
|
||||
path,
|
||||
response_model=response_model,
|
||||
|
|
@ -521,7 +561,9 @@ class FastAPI(Starlette):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class or self.default_response_class,
|
||||
name=name,
|
||||
|
|
@ -544,11 +586,14 @@ class FastAPI(Starlette):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.router.patch(
|
||||
path,
|
||||
response_model=response_model,
|
||||
|
|
@ -564,7 +609,9 @@ class FastAPI(Starlette):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class or self.default_response_class,
|
||||
name=name,
|
||||
|
|
@ -587,11 +634,14 @@ class FastAPI(Starlette):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.router.trace(
|
||||
path,
|
||||
response_model=response_model,
|
||||
|
|
@ -607,7 +657,9 @@ class FastAPI(Starlette):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class or self.default_response_class,
|
||||
name=name,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from typing import Any, Callable
|
||||
|
||||
from starlette.concurrency import iterate_in_threadpool, run_in_threadpool # noqa
|
||||
from starlette.concurrency import iterate_in_threadpool # noqa
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
|
||||
asynccontextmanager_error_message = """
|
||||
FastAPI's contextmanager_in_threadpool require Python 3.7 or above,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
from typing import Callable, List, Sequence
|
||||
|
||||
from fastapi.security.base import SecurityBase
|
||||
from pydantic.fields import Field
|
||||
|
||||
try:
|
||||
from pydantic.fields import ModelField
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic.fields import Field as ModelField # type: ignore
|
||||
|
||||
param_supported_types = (str, int, float, bool)
|
||||
|
||||
|
|
@ -16,11 +21,11 @@ class Dependant:
|
|||
def __init__(
|
||||
self,
|
||||
*,
|
||||
path_params: List[Field] = None,
|
||||
query_params: List[Field] = None,
|
||||
header_params: List[Field] = None,
|
||||
cookie_params: List[Field] = None,
|
||||
body_params: List[Field] = None,
|
||||
path_params: List[ModelField] = None,
|
||||
query_params: List[ModelField] = None,
|
||||
header_params: List[ModelField] = None,
|
||||
cookie_params: List[ModelField] = None,
|
||||
body_params: List[ModelField] = None,
|
||||
dependencies: List["Dependant"] = None,
|
||||
security_schemes: List[SecurityRequirement] = None,
|
||||
name: str = None,
|
||||
|
|
|
|||
|
|
@ -27,13 +27,11 @@ from fastapi.dependencies.models import Dependant, SecurityRequirement
|
|||
from fastapi.security.base import SecurityBase
|
||||
from fastapi.security.oauth2 import OAuth2, SecurityScopes
|
||||
from fastapi.security.open_id_connect_url import OpenIdConnect
|
||||
from fastapi.utils import get_path_param_names
|
||||
from pydantic import BaseConfig, BaseModel, Schema, create_model
|
||||
from fastapi.utils import PYDANTIC_1, get_field_info, get_path_param_names
|
||||
from pydantic import BaseConfig, BaseModel, create_model
|
||||
from pydantic.error_wrappers import ErrorWrapper
|
||||
from pydantic.errors import MissingError
|
||||
from pydantic.fields import Field, Required, Shape
|
||||
from pydantic.schema import get_annotation_from_schema
|
||||
from pydantic.utils import ForwardRef, evaluate_forwardref, lenient_issubclass
|
||||
from pydantic.utils import lenient_issubclass
|
||||
from starlette.background import BackgroundTasks
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
from starlette.datastructures import FormData, Headers, QueryParams, UploadFile
|
||||
|
|
@ -41,20 +39,55 @@ from starlette.requests import Request
|
|||
from starlette.responses import Response
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
try:
|
||||
from pydantic.fields import (
|
||||
SHAPE_LIST,
|
||||
SHAPE_SEQUENCE,
|
||||
SHAPE_SET,
|
||||
SHAPE_SINGLETON,
|
||||
SHAPE_TUPLE,
|
||||
SHAPE_TUPLE_ELLIPSIS,
|
||||
FieldInfo,
|
||||
ModelField,
|
||||
Required,
|
||||
)
|
||||
from pydantic.schema import get_annotation_from_field_info
|
||||
from pydantic.typing import ForwardRef, evaluate_forwardref
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic.fields import Field as ModelField # type: ignore
|
||||
from pydantic.fields import Required, Shape # type: ignore
|
||||
from pydantic import Schema as FieldInfo # type: ignore
|
||||
from pydantic.schema import get_annotation_from_schema # type: ignore
|
||||
from pydantic.utils import ForwardRef, evaluate_forwardref # type: ignore
|
||||
|
||||
SHAPE_LIST = Shape.LIST
|
||||
SHAPE_SEQUENCE = Shape.SEQUENCE
|
||||
SHAPE_SET = Shape.SET
|
||||
SHAPE_SINGLETON = Shape.SINGLETON
|
||||
SHAPE_TUPLE = Shape.TUPLE
|
||||
SHAPE_TUPLE_ELLIPSIS = Shape.TUPLE_ELLIPS
|
||||
|
||||
def get_annotation_from_field_info(
|
||||
annotation: Any, field_info: FieldInfo, field_name: str
|
||||
) -> Type[Any]:
|
||||
return get_annotation_from_schema(annotation, field_info)
|
||||
|
||||
|
||||
sequence_shapes = {
|
||||
Shape.LIST,
|
||||
Shape.SET,
|
||||
Shape.TUPLE,
|
||||
Shape.SEQUENCE,
|
||||
Shape.TUPLE_ELLIPS,
|
||||
SHAPE_LIST,
|
||||
SHAPE_SET,
|
||||
SHAPE_TUPLE,
|
||||
SHAPE_SEQUENCE,
|
||||
SHAPE_TUPLE_ELLIPSIS,
|
||||
}
|
||||
sequence_types = (list, set, tuple)
|
||||
sequence_shape_to_type = {
|
||||
Shape.LIST: list,
|
||||
Shape.SET: set,
|
||||
Shape.TUPLE: tuple,
|
||||
Shape.SEQUENCE: list,
|
||||
Shape.TUPLE_ELLIPS: list,
|
||||
SHAPE_LIST: list,
|
||||
SHAPE_SET: set,
|
||||
SHAPE_TUPLE: tuple,
|
||||
SHAPE_SEQUENCE: list,
|
||||
SHAPE_TUPLE_ELLIPSIS: list,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -150,12 +183,13 @@ def get_flat_dependant(
|
|||
return flat_dependant
|
||||
|
||||
|
||||
def is_scalar_field(field: Field) -> bool:
|
||||
def is_scalar_field(field: ModelField) -> bool:
|
||||
field_info = get_field_info(field)
|
||||
if not (
|
||||
field.shape == Shape.SINGLETON
|
||||
field.shape == SHAPE_SINGLETON
|
||||
and not lenient_issubclass(field.type_, BaseModel)
|
||||
and not lenient_issubclass(field.type_, sequence_types + (dict,))
|
||||
and not isinstance(field.schema, params.Body)
|
||||
and not isinstance(field_info, params.Body)
|
||||
):
|
||||
return False
|
||||
if field.sub_fields:
|
||||
|
|
@ -164,7 +198,7 @@ def is_scalar_field(field: Field) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def is_scalar_sequence_field(field: Field) -> bool:
|
||||
def is_scalar_sequence_field(field: ModelField) -> bool:
|
||||
if (field.shape in sequence_shapes) and not lenient_issubclass(
|
||||
field.type_, BaseModel
|
||||
):
|
||||
|
|
@ -239,7 +273,9 @@ def get_dependant(
|
|||
continue
|
||||
if add_non_field_param_to_dependency(param=param, dependant=dependant):
|
||||
continue
|
||||
param_field = get_param_field(param=param, default_schema=params.Query)
|
||||
param_field = get_param_field(
|
||||
param=param, default_field_info=params.Query, param_name=param_name
|
||||
)
|
||||
if param_name in path_param_names:
|
||||
assert is_scalar_field(
|
||||
field=param_field
|
||||
|
|
@ -250,7 +286,8 @@ def get_dependant(
|
|||
ignore_default = True
|
||||
param_field = get_param_field(
|
||||
param=param,
|
||||
default_schema=params.Path,
|
||||
param_name=param_name,
|
||||
default_field_info=params.Path,
|
||||
force_type=params.ParamTypes.path,
|
||||
ignore_default=ignore_default,
|
||||
)
|
||||
|
|
@ -262,8 +299,9 @@ def get_dependant(
|
|||
) and is_scalar_sequence_field(param_field):
|
||||
add_param_to_fields(field=param_field, dependant=dependant)
|
||||
else:
|
||||
field_info = get_field_info(param_field)
|
||||
assert isinstance(
|
||||
param_field.schema, params.Body
|
||||
field_info, params.Body
|
||||
), f"Param: {param_field.name} can only be a request body, using Body(...)"
|
||||
dependant.body_params.append(param_field)
|
||||
return dependant
|
||||
|
|
@ -293,59 +331,82 @@ def add_non_field_param_to_dependency(
|
|||
def get_param_field(
|
||||
*,
|
||||
param: inspect.Parameter,
|
||||
default_schema: Type[params.Param] = params.Param,
|
||||
param_name: str,
|
||||
default_field_info: Type[params.Param] = params.Param,
|
||||
force_type: params.ParamTypes = None,
|
||||
ignore_default: bool = False,
|
||||
) -> Field:
|
||||
) -> ModelField:
|
||||
default_value = Required
|
||||
had_schema = False
|
||||
if not param.default == param.empty and ignore_default is False:
|
||||
default_value = param.default
|
||||
if isinstance(default_value, Schema):
|
||||
if isinstance(default_value, FieldInfo):
|
||||
had_schema = True
|
||||
schema = default_value
|
||||
default_value = schema.default
|
||||
if isinstance(schema, params.Param) and getattr(schema, "in_", None) is None:
|
||||
schema.in_ = default_schema.in_
|
||||
field_info = default_value
|
||||
default_value = field_info.default
|
||||
if (
|
||||
isinstance(field_info, params.Param)
|
||||
and getattr(field_info, "in_", None) is None
|
||||
):
|
||||
field_info.in_ = default_field_info.in_
|
||||
if force_type:
|
||||
schema.in_ = force_type # type: ignore
|
||||
field_info.in_ = force_type # type: ignore
|
||||
else:
|
||||
schema = default_schema(default_value)
|
||||
field_info = default_field_info(default_value)
|
||||
required = default_value == Required
|
||||
annotation: Any = Any
|
||||
if not param.annotation == param.empty:
|
||||
annotation = param.annotation
|
||||
annotation = get_annotation_from_schema(annotation, schema)
|
||||
if not schema.alias and getattr(schema, "convert_underscores", None):
|
||||
annotation = get_annotation_from_field_info(annotation, field_info, param_name)
|
||||
if not field_info.alias and getattr(field_info, "convert_underscores", None):
|
||||
alias = param.name.replace("_", "-")
|
||||
else:
|
||||
alias = schema.alias or param.name
|
||||
field = Field(
|
||||
name=param.name,
|
||||
type_=annotation,
|
||||
default=None if required else default_value,
|
||||
alias=alias,
|
||||
required=required,
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
schema=schema,
|
||||
)
|
||||
alias = field_info.alias or param.name
|
||||
if PYDANTIC_1:
|
||||
field = ModelField(
|
||||
name=param.name,
|
||||
type_=annotation,
|
||||
default=None if required else default_value,
|
||||
alias=alias,
|
||||
required=required,
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
field_info=field_info,
|
||||
)
|
||||
# TODO: remove when removing support for Pydantic < 1.2.0
|
||||
field.required = required
|
||||
else: # pragma: nocover
|
||||
field = ModelField( # type: ignore
|
||||
name=param.name,
|
||||
type_=annotation,
|
||||
default=None if required else default_value,
|
||||
alias=alias,
|
||||
required=required,
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
schema=field_info,
|
||||
)
|
||||
field.required = required
|
||||
if not had_schema and not is_scalar_field(field=field):
|
||||
field.schema = params.Body(schema.default)
|
||||
if PYDANTIC_1:
|
||||
field.field_info = params.Body(field_info.default)
|
||||
else:
|
||||
field.schema = params.Body(field_info.default) # type: ignore # pragma: nocover
|
||||
|
||||
return field
|
||||
|
||||
|
||||
def add_param_to_fields(*, field: Field, dependant: Dependant) -> None:
|
||||
field.schema = cast(params.Param, field.schema)
|
||||
if field.schema.in_ == params.ParamTypes.path:
|
||||
def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
|
||||
field_info = cast(params.Param, get_field_info(field))
|
||||
if field_info.in_ == params.ParamTypes.path:
|
||||
dependant.path_params.append(field)
|
||||
elif field.schema.in_ == params.ParamTypes.query:
|
||||
elif field_info.in_ == params.ParamTypes.query:
|
||||
dependant.query_params.append(field)
|
||||
elif field.schema.in_ == params.ParamTypes.header:
|
||||
elif field_info.in_ == params.ParamTypes.header:
|
||||
dependant.header_params.append(field)
|
||||
else:
|
||||
assert (
|
||||
field.schema.in_ == params.ParamTypes.cookie
|
||||
field_info.in_ == params.ParamTypes.cookie
|
||||
), f"non-body parameters must be in path, query, header or cookie: {field.name}"
|
||||
dependant.cookie_params.append(field)
|
||||
|
||||
|
|
@ -506,7 +567,7 @@ async def solve_dependencies(
|
|||
|
||||
|
||||
def request_params_to_args(
|
||||
required_params: Sequence[Field],
|
||||
required_params: Sequence[ModelField],
|
||||
received_params: Union[Mapping[str, Any], QueryParams, Headers],
|
||||
) -> Tuple[Dict[str, Any], List[ErrorWrapper]]:
|
||||
values = {}
|
||||
|
|
@ -518,21 +579,32 @@ def request_params_to_args(
|
|||
value = received_params.getlist(field.alias) or field.default
|
||||
else:
|
||||
value = received_params.get(field.alias)
|
||||
schema = field.schema
|
||||
assert isinstance(schema, params.Param), "Params must be subclasses of Param"
|
||||
field_info = get_field_info(field)
|
||||
assert isinstance(
|
||||
field_info, params.Param
|
||||
), "Params must be subclasses of Param"
|
||||
if value is None:
|
||||
if field.required:
|
||||
errors.append(
|
||||
ErrorWrapper(
|
||||
MissingError(),
|
||||
loc=(schema.in_.value, field.alias),
|
||||
config=BaseConfig,
|
||||
if PYDANTIC_1:
|
||||
errors.append(
|
||||
ErrorWrapper(
|
||||
MissingError(), loc=(field_info.in_.value, field.alias)
|
||||
)
|
||||
)
|
||||
else: # pragma: nocover
|
||||
errors.append(
|
||||
ErrorWrapper( # type: ignore
|
||||
MissingError(),
|
||||
loc=(field_info.in_.value, field.alias),
|
||||
config=BaseConfig,
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
values[field.name] = deepcopy(field.default)
|
||||
continue
|
||||
v_, errors_ = field.validate(value, values, loc=(schema.in_.value, field.alias))
|
||||
v_, errors_ = field.validate(
|
||||
value, values, loc=(field_info.in_.value, field.alias)
|
||||
)
|
||||
if isinstance(errors_, ErrorWrapper):
|
||||
errors.append(errors_)
|
||||
elif isinstance(errors_, list):
|
||||
|
|
@ -543,14 +615,15 @@ def request_params_to_args(
|
|||
|
||||
|
||||
async def request_body_to_args(
|
||||
required_params: List[Field],
|
||||
required_params: List[ModelField],
|
||||
received_body: Optional[Union[Dict[str, Any], FormData]],
|
||||
) -> Tuple[Dict[str, Any], List[ErrorWrapper]]:
|
||||
values = {}
|
||||
errors = []
|
||||
if required_params:
|
||||
field = required_params[0]
|
||||
embed = getattr(field.schema, "embed", None)
|
||||
field_info = get_field_info(field)
|
||||
embed = getattr(field_info, "embed", None)
|
||||
if len(required_params) == 1 and not embed:
|
||||
received_body = {field.alias: received_body}
|
||||
for field in required_params:
|
||||
|
|
@ -564,31 +637,38 @@ async def request_body_to_args(
|
|||
value = received_body.get(field.alias)
|
||||
if (
|
||||
value is None
|
||||
or (isinstance(field.schema, params.Form) and value == "")
|
||||
or (isinstance(field_info, params.Form) and value == "")
|
||||
or (
|
||||
isinstance(field.schema, params.Form)
|
||||
isinstance(field_info, params.Form)
|
||||
and field.shape in sequence_shapes
|
||||
and len(value) == 0
|
||||
)
|
||||
):
|
||||
if field.required:
|
||||
errors.append(
|
||||
ErrorWrapper(
|
||||
MissingError(), loc=("body", field.alias), config=BaseConfig
|
||||
if PYDANTIC_1:
|
||||
errors.append(
|
||||
ErrorWrapper(MissingError(), loc=("body", field.alias))
|
||||
)
|
||||
else: # pragma: nocover
|
||||
errors.append(
|
||||
ErrorWrapper( # type: ignore
|
||||
MissingError(),
|
||||
loc=("body", field.alias),
|
||||
config=BaseConfig,
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
values[field.name] = deepcopy(field.default)
|
||||
continue
|
||||
if (
|
||||
isinstance(field.schema, params.File)
|
||||
isinstance(field_info, params.File)
|
||||
and lenient_issubclass(field.type_, bytes)
|
||||
and isinstance(value, UploadFile)
|
||||
):
|
||||
value = await value.read()
|
||||
elif (
|
||||
field.shape in sequence_shapes
|
||||
and isinstance(field.schema, params.File)
|
||||
and isinstance(field_info, params.File)
|
||||
and lenient_issubclass(field.type_, bytes)
|
||||
and isinstance(value, sequence_types)
|
||||
):
|
||||
|
|
@ -605,31 +685,45 @@ async def request_body_to_args(
|
|||
return values, errors
|
||||
|
||||
|
||||
def get_schema_compatible_field(*, field: Field) -> Field:
|
||||
def get_schema_compatible_field(*, field: ModelField) -> ModelField:
|
||||
out_field = field
|
||||
if lenient_issubclass(field.type_, UploadFile):
|
||||
use_type: type = bytes
|
||||
if field.shape in sequence_shapes:
|
||||
use_type = List[bytes]
|
||||
out_field = Field(
|
||||
name=field.name,
|
||||
type_=use_type,
|
||||
class_validators=field.class_validators,
|
||||
model_config=field.model_config,
|
||||
default=field.default,
|
||||
required=field.required,
|
||||
alias=field.alias,
|
||||
schema=field.schema,
|
||||
)
|
||||
if PYDANTIC_1:
|
||||
out_field = ModelField(
|
||||
name=field.name,
|
||||
type_=use_type,
|
||||
class_validators=field.class_validators,
|
||||
model_config=field.model_config,
|
||||
default=field.default,
|
||||
required=field.required,
|
||||
alias=field.alias,
|
||||
field_info=field.field_info,
|
||||
)
|
||||
else: # pragma: nocover
|
||||
out_field = ModelField( # type: ignore
|
||||
name=field.name,
|
||||
type_=use_type,
|
||||
class_validators=field.class_validators,
|
||||
model_config=field.model_config,
|
||||
default=field.default,
|
||||
required=field.required,
|
||||
alias=field.alias,
|
||||
schema=field.schema, # type: ignore
|
||||
)
|
||||
|
||||
return out_field
|
||||
|
||||
|
||||
def get_body_field(*, dependant: Dependant, name: str) -> Optional[Field]:
|
||||
def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
|
||||
flat_dependant = get_flat_dependant(dependant)
|
||||
if not flat_dependant.body_params:
|
||||
return None
|
||||
first_param = flat_dependant.body_params[0]
|
||||
embed = getattr(first_param.schema, "embed", None)
|
||||
field_info = get_field_info(first_param)
|
||||
embed = getattr(field_info, "embed", None)
|
||||
if len(flat_dependant.body_params) == 1 and not embed:
|
||||
return get_schema_compatible_field(field=first_param)
|
||||
model_name = "Body_" + name
|
||||
|
|
@ -638,30 +732,45 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[Field]:
|
|||
BodyModel.__fields__[f.name] = get_schema_compatible_field(field=f)
|
||||
required = any(True for f in flat_dependant.body_params if f.required)
|
||||
|
||||
BodySchema_kwargs: Dict[str, Any] = dict(default=None)
|
||||
if any(isinstance(f.schema, params.File) for f in flat_dependant.body_params):
|
||||
BodySchema: Type[params.Body] = params.File
|
||||
elif any(isinstance(f.schema, params.Form) for f in flat_dependant.body_params):
|
||||
BodySchema = params.Form
|
||||
BodyFieldInfo_kwargs: Dict[str, Any] = dict(default=None)
|
||||
if any(
|
||||
isinstance(get_field_info(f), params.File) for f in flat_dependant.body_params
|
||||
):
|
||||
BodyFieldInfo: Type[params.Body] = params.File
|
||||
elif any(
|
||||
isinstance(get_field_info(f), params.Form) for f in flat_dependant.body_params
|
||||
):
|
||||
BodyFieldInfo = params.Form
|
||||
else:
|
||||
BodySchema = params.Body
|
||||
BodyFieldInfo = params.Body
|
||||
|
||||
body_param_media_types = [
|
||||
getattr(f.schema, "media_type")
|
||||
getattr(get_field_info(f), "media_type")
|
||||
for f in flat_dependant.body_params
|
||||
if isinstance(f.schema, params.Body)
|
||||
if isinstance(get_field_info(f), params.Body)
|
||||
]
|
||||
if len(set(body_param_media_types)) == 1:
|
||||
BodySchema_kwargs["media_type"] = body_param_media_types[0]
|
||||
|
||||
field = Field(
|
||||
name="body",
|
||||
type_=BodyModel,
|
||||
default=None,
|
||||
required=required,
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
alias="body",
|
||||
schema=BodySchema(**BodySchema_kwargs),
|
||||
)
|
||||
BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]
|
||||
if PYDANTIC_1:
|
||||
field = ModelField(
|
||||
name="body",
|
||||
type_=BodyModel,
|
||||
default=None,
|
||||
required=required,
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
alias="body",
|
||||
field_info=BodyFieldInfo(**BodyFieldInfo_kwargs),
|
||||
)
|
||||
else: # pragma: nocover
|
||||
field = ModelField( # type: ignore
|
||||
name="body",
|
||||
type_=BodyModel,
|
||||
default=None,
|
||||
required=required,
|
||||
model_config=BaseConfig,
|
||||
class_validators={},
|
||||
alias="body",
|
||||
schema=BodyFieldInfo(**BodyFieldInfo_kwargs),
|
||||
)
|
||||
return field
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from enum import Enum
|
|||
from types import GeneratorType
|
||||
from typing import Any, Dict, List, Set, Union
|
||||
|
||||
from fastapi.utils import PYDANTIC_1, logger
|
||||
from pydantic import BaseModel
|
||||
from pydantic.json import ENCODERS_BY_TYPE
|
||||
|
||||
|
|
@ -14,24 +15,40 @@ def jsonable_encoder(
|
|||
include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
by_alias: bool = True,
|
||||
skip_defaults: bool = False,
|
||||
skip_defaults: bool = None,
|
||||
exclude_unset: bool = False,
|
||||
include_none: bool = True,
|
||||
custom_encoder: dict = {},
|
||||
sqlalchemy_safe: bool = True,
|
||||
) -> Any:
|
||||
if skip_defaults is not None:
|
||||
logger.warning( # pragma: nocover
|
||||
"skip_defaults in jsonable_encoder has been deprecated in \
|
||||
favor of exclude_unset to keep in line with Pydantic v1, support for it \
|
||||
will be removed soon."
|
||||
)
|
||||
if include is not None and not isinstance(include, set):
|
||||
include = set(include)
|
||||
if exclude is not None and not isinstance(exclude, set):
|
||||
exclude = set(exclude)
|
||||
if isinstance(obj, BaseModel):
|
||||
encoder = getattr(obj.Config, "json_encoders", custom_encoder)
|
||||
return jsonable_encoder(
|
||||
obj.dict(
|
||||
if PYDANTIC_1:
|
||||
obj_dict = obj.dict(
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
by_alias=by_alias,
|
||||
skip_defaults=skip_defaults,
|
||||
),
|
||||
exclude_unset=bool(exclude_unset or skip_defaults),
|
||||
)
|
||||
else: # pragma: nocover
|
||||
obj_dict = obj.dict(
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
by_alias=by_alias,
|
||||
skip_defaults=bool(exclude_unset or skip_defaults),
|
||||
)
|
||||
return jsonable_encoder(
|
||||
obj_dict,
|
||||
include_none=include_none,
|
||||
custom_encoder=encoder,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
|
|
@ -55,7 +72,7 @@ def jsonable_encoder(
|
|||
encoded_key = jsonable_encoder(
|
||||
key,
|
||||
by_alias=by_alias,
|
||||
skip_defaults=skip_defaults,
|
||||
exclude_unset=exclude_unset,
|
||||
include_none=include_none,
|
||||
custom_encoder=custom_encoder,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
|
|
@ -63,7 +80,7 @@ def jsonable_encoder(
|
|||
encoded_value = jsonable_encoder(
|
||||
value,
|
||||
by_alias=by_alias,
|
||||
skip_defaults=skip_defaults,
|
||||
exclude_unset=exclude_unset,
|
||||
include_none=include_none,
|
||||
custom_encoder=custom_encoder,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
|
|
@ -79,7 +96,7 @@ def jsonable_encoder(
|
|||
include=include,
|
||||
exclude=exclude,
|
||||
by_alias=by_alias,
|
||||
skip_defaults=skip_defaults,
|
||||
exclude_unset=exclude_unset,
|
||||
include_none=include_none,
|
||||
custom_encoder=custom_encoder,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
|
|
@ -107,7 +124,7 @@ def jsonable_encoder(
|
|||
return jsonable_encoder(
|
||||
data,
|
||||
by_alias=by_alias,
|
||||
skip_defaults=skip_defaults,
|
||||
exclude_unset=exclude_unset,
|
||||
include_none=include_none,
|
||||
custom_encoder=custom_encoder,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from typing import Any, Sequence
|
||||
|
||||
from pydantic import ValidationError
|
||||
from fastapi.utils import PYDANTIC_1
|
||||
from pydantic import ValidationError, create_model
|
||||
from pydantic.error_wrappers import ErrorList
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
from starlette.requests import Request
|
||||
|
|
@ -15,11 +16,21 @@ class HTTPException(StarletteHTTPException):
|
|||
self.headers = headers
|
||||
|
||||
|
||||
RequestErrorModel = create_model("Request")
|
||||
WebSocketErrorModel = create_model("WebSocket")
|
||||
|
||||
|
||||
class RequestValidationError(ValidationError):
|
||||
def __init__(self, errors: Sequence[ErrorList]) -> None:
|
||||
super().__init__(errors, Request)
|
||||
if PYDANTIC_1:
|
||||
super().__init__(errors, RequestErrorModel)
|
||||
else:
|
||||
super().__init__(errors, Request) # type: ignore # pragma: nocover
|
||||
|
||||
|
||||
class WebSocketRequestValidationError(ValidationError):
|
||||
def __init__(self, errors: Sequence[ErrorList]) -> None:
|
||||
super().__init__(errors, WebSocket)
|
||||
if PYDANTIC_1:
|
||||
super().__init__(errors, WebSocketErrorModel)
|
||||
else:
|
||||
super().__init__(errors, WebSocket) # type: ignore # pragma: nocover
|
||||
|
|
|
|||
|
|
@ -1,17 +1,25 @@
|
|||
import logging
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Schema as PSchema
|
||||
from pydantic.types import UrlStr
|
||||
from fastapi.utils import logger
|
||||
from pydantic import BaseModel
|
||||
|
||||
logger = logging.getLogger("fastapi")
|
||||
try:
|
||||
from pydantic import AnyUrl, Field
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic import Schema as Field # type: ignore
|
||||
from pydantic import UrlStr as AnyUrl # type: ignore
|
||||
|
||||
try:
|
||||
import email_validator
|
||||
|
||||
assert email_validator # make autoflake ignore the unused import
|
||||
from pydantic.types import EmailStr
|
||||
try:
|
||||
from pydantic import EmailStr
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic.types import EmailStr # type: ignore
|
||||
except ImportError: # pragma: no cover
|
||||
logger.warning(
|
||||
"email-validator not installed, email fields will be treated as str.\n"
|
||||
|
|
@ -24,13 +32,13 @@ except ImportError: # pragma: no cover
|
|||
|
||||
class Contact(BaseModel):
|
||||
name: Optional[str] = None
|
||||
url: Optional[UrlStr] = None
|
||||
url: Optional[AnyUrl] = None
|
||||
email: Optional[EmailStr] = None
|
||||
|
||||
|
||||
class License(BaseModel):
|
||||
name: str
|
||||
url: Optional[UrlStr] = None
|
||||
url: Optional[AnyUrl] = None
|
||||
|
||||
|
||||
class Info(BaseModel):
|
||||
|
|
@ -49,13 +57,13 @@ class ServerVariable(BaseModel):
|
|||
|
||||
|
||||
class Server(BaseModel):
|
||||
url: UrlStr
|
||||
url: AnyUrl
|
||||
description: Optional[str] = None
|
||||
variables: Optional[Dict[str, ServerVariable]] = None
|
||||
|
||||
|
||||
class Reference(BaseModel):
|
||||
ref: str = PSchema(..., alias="$ref") # type: ignore
|
||||
ref: str = Field(..., alias="$ref")
|
||||
|
||||
|
||||
class Discriminator(BaseModel):
|
||||
|
|
@ -73,32 +81,32 @@ class XML(BaseModel):
|
|||
|
||||
class ExternalDocumentation(BaseModel):
|
||||
description: Optional[str] = None
|
||||
url: UrlStr
|
||||
url: AnyUrl
|
||||
|
||||
|
||||
class SchemaBase(BaseModel):
|
||||
ref: Optional[str] = PSchema(None, alias="$ref") # type: ignore
|
||||
ref: Optional[str] = Field(None, alias="$ref")
|
||||
title: Optional[str] = None
|
||||
multipleOf: Optional[float] = None
|
||||
maximum: Optional[float] = None
|
||||
exclusiveMaximum: Optional[float] = None
|
||||
minimum: Optional[float] = None
|
||||
exclusiveMinimum: Optional[float] = None
|
||||
maxLength: Optional[int] = PSchema(None, gte=0) # type: ignore
|
||||
minLength: Optional[int] = PSchema(None, gte=0) # type: ignore
|
||||
maxLength: Optional[int] = Field(None, gte=0)
|
||||
minLength: Optional[int] = Field(None, gte=0)
|
||||
pattern: Optional[str] = None
|
||||
maxItems: Optional[int] = PSchema(None, gte=0) # type: ignore
|
||||
minItems: Optional[int] = PSchema(None, gte=0) # type: ignore
|
||||
maxItems: Optional[int] = Field(None, gte=0)
|
||||
minItems: Optional[int] = Field(None, gte=0)
|
||||
uniqueItems: Optional[bool] = None
|
||||
maxProperties: Optional[int] = PSchema(None, gte=0) # type: ignore
|
||||
minProperties: Optional[int] = PSchema(None, gte=0) # type: ignore
|
||||
maxProperties: Optional[int] = Field(None, gte=0)
|
||||
minProperties: Optional[int] = Field(None, gte=0)
|
||||
required: Optional[List[str]] = None
|
||||
enum: Optional[List[str]] = None
|
||||
type: Optional[str] = None
|
||||
allOf: Optional[List[Any]] = None
|
||||
oneOf: Optional[List[Any]] = None
|
||||
anyOf: Optional[List[Any]] = None
|
||||
not_: Optional[List[Any]] = PSchema(None, alias="not") # type: ignore
|
||||
not_: Optional[List[Any]] = Field(None, alias="not")
|
||||
items: Optional[Any] = None
|
||||
properties: Optional[Dict[str, Any]] = None
|
||||
additionalProperties: Optional[Union[Dict[str, Any], bool]] = None
|
||||
|
|
@ -119,17 +127,17 @@ class Schema(SchemaBase):
|
|||
allOf: Optional[List[SchemaBase]] = None
|
||||
oneOf: Optional[List[SchemaBase]] = None
|
||||
anyOf: Optional[List[SchemaBase]] = None
|
||||
not_: Optional[List[SchemaBase]] = PSchema(None, alias="not") # type: ignore
|
||||
not_: Optional[List[SchemaBase]] = Field(None, alias="not")
|
||||
items: Optional[SchemaBase] = None
|
||||
properties: Optional[Dict[str, SchemaBase]] = None
|
||||
additionalProperties: Optional[Union[SchemaBase, bool]] = None # type: ignore
|
||||
additionalProperties: Optional[Union[Dict[str, Any], bool]] = None
|
||||
|
||||
|
||||
class Example(BaseModel):
|
||||
summary: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
value: Optional[Any] = None
|
||||
externalValue: Optional[UrlStr] = None
|
||||
externalValue: Optional[AnyUrl] = None
|
||||
|
||||
|
||||
class ParameterInType(Enum):
|
||||
|
|
@ -149,9 +157,7 @@ class Encoding(BaseModel):
|
|||
|
||||
|
||||
class MediaType(BaseModel):
|
||||
schema_: Optional[Union[Schema, Reference]] = PSchema( # type: ignore
|
||||
None, alias="schema"
|
||||
)
|
||||
schema_: Optional[Union[Schema, Reference]] = Field(None, alias="schema")
|
||||
example: Optional[Any] = None
|
||||
examples: Optional[Dict[str, Union[Example, Reference]]] = None
|
||||
encoding: Optional[Dict[str, Encoding]] = None
|
||||
|
|
@ -165,9 +171,7 @@ class ParameterBase(BaseModel):
|
|||
style: Optional[str] = None
|
||||
explode: Optional[bool] = None
|
||||
allowReserved: Optional[bool] = None
|
||||
schema_: Optional[Union[Schema, Reference]] = PSchema( # type: ignore
|
||||
None, alias="schema"
|
||||
)
|
||||
schema_: Optional[Union[Schema, Reference]] = Field(None, alias="schema")
|
||||
example: Optional[Any] = None
|
||||
examples: Optional[Dict[str, Union[Example, Reference]]] = None
|
||||
# Serialization rules for more complex scenarios
|
||||
|
|
@ -176,7 +180,7 @@ class ParameterBase(BaseModel):
|
|||
|
||||
class Parameter(ParameterBase):
|
||||
name: str
|
||||
in_: ParameterInType = PSchema(..., alias="in") # type: ignore
|
||||
in_: ParameterInType = Field(..., alias="in")
|
||||
|
||||
|
||||
class Header(ParameterBase):
|
||||
|
|
@ -227,7 +231,7 @@ class Operation(BaseModel):
|
|||
|
||||
|
||||
class PathItem(BaseModel):
|
||||
ref: Optional[str] = PSchema(None, alias="$ref") # type: ignore
|
||||
ref: Optional[str] = Field(None, alias="$ref")
|
||||
summary: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
get: Optional[Operation] = None
|
||||
|
|
@ -255,7 +259,7 @@ class SecuritySchemeType(Enum):
|
|||
|
||||
|
||||
class SecurityBase(BaseModel):
|
||||
type_: SecuritySchemeType = PSchema(..., alias="type") # type: ignore
|
||||
type_: SecuritySchemeType = Field(..., alias="type")
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
|
|
@ -266,13 +270,13 @@ class APIKeyIn(Enum):
|
|||
|
||||
|
||||
class APIKey(SecurityBase):
|
||||
type_ = PSchema(SecuritySchemeType.apiKey, alias="type") # type: ignore
|
||||
in_: APIKeyIn = PSchema(..., alias="in") # type: ignore
|
||||
type_ = Field(SecuritySchemeType.apiKey, alias="type")
|
||||
in_: APIKeyIn = Field(..., alias="in")
|
||||
name: str
|
||||
|
||||
|
||||
class HTTPBase(SecurityBase):
|
||||
type_ = PSchema(SecuritySchemeType.http, alias="type") # type: ignore
|
||||
type_ = Field(SecuritySchemeType.http, alias="type")
|
||||
scheme: str
|
||||
|
||||
|
||||
|
|
@ -311,12 +315,12 @@ class OAuthFlows(BaseModel):
|
|||
|
||||
|
||||
class OAuth2(SecurityBase):
|
||||
type_ = PSchema(SecuritySchemeType.oauth2, alias="type") # type: ignore
|
||||
type_ = Field(SecuritySchemeType.oauth2, alias="type")
|
||||
flows: OAuthFlows
|
||||
|
||||
|
||||
class OpenIdConnect(SecurityBase):
|
||||
type_ = PSchema(SecuritySchemeType.openIdConnect, alias="type") # type: ignore
|
||||
type_ = Field(SecuritySchemeType.openIdConnect, alias="type")
|
||||
openIdConnectUrl: str
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,16 +14,23 @@ from fastapi.openapi.models import OpenAPI
|
|||
from fastapi.params import Body, Param
|
||||
from fastapi.utils import (
|
||||
generate_operation_id_for_path,
|
||||
get_field_info,
|
||||
get_flat_models_from_routes,
|
||||
get_model_definitions,
|
||||
)
|
||||
from pydantic.fields import Field
|
||||
from pydantic import BaseModel
|
||||
from pydantic.schema import field_schema, get_model_name_map
|
||||
from pydantic.utils import lenient_issubclass
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.routing import BaseRoute
|
||||
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
try:
|
||||
from pydantic.fields import ModelField
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic.fields import Field as ModelField # type: ignore
|
||||
|
||||
validation_error_definition = {
|
||||
"title": "ValidationError",
|
||||
"type": "object",
|
||||
|
|
@ -57,7 +64,7 @@ status_code_ranges: Dict[str, str] = {
|
|||
}
|
||||
|
||||
|
||||
def get_openapi_params(dependant: Dependant) -> List[Field]:
|
||||
def get_openapi_params(dependant: Dependant) -> List[ModelField]:
|
||||
flat_dependant = get_flat_dependant(dependant, skip_repeats=True)
|
||||
return (
|
||||
flat_dependant.path_params
|
||||
|
|
@ -83,37 +90,37 @@ def get_openapi_security_definitions(flat_dependant: Dependant) -> Tuple[Dict, L
|
|||
|
||||
|
||||
def get_openapi_operation_parameters(
|
||||
all_route_params: Sequence[Field],
|
||||
all_route_params: Sequence[ModelField],
|
||||
) -> List[Dict[str, Any]]:
|
||||
parameters = []
|
||||
for param in all_route_params:
|
||||
schema = param.schema
|
||||
schema = cast(Param, schema)
|
||||
field_info = get_field_info(param)
|
||||
field_info = cast(Param, field_info)
|
||||
parameter = {
|
||||
"name": param.alias,
|
||||
"in": schema.in_.value,
|
||||
"in": field_info.in_.value,
|
||||
"required": param.required,
|
||||
"schema": field_schema(param, model_name_map={})[0],
|
||||
}
|
||||
if schema.description:
|
||||
parameter["description"] = schema.description
|
||||
if schema.deprecated:
|
||||
parameter["deprecated"] = schema.deprecated
|
||||
if field_info.description:
|
||||
parameter["description"] = field_info.description
|
||||
if field_info.deprecated:
|
||||
parameter["deprecated"] = field_info.deprecated
|
||||
parameters.append(parameter)
|
||||
return parameters
|
||||
|
||||
|
||||
def get_openapi_operation_request_body(
|
||||
*, body_field: Optional[Field], model_name_map: Dict[Type, str]
|
||||
*, body_field: Optional[ModelField], model_name_map: Dict[Type[BaseModel], str]
|
||||
) -> Optional[Dict]:
|
||||
if not body_field:
|
||||
return None
|
||||
assert isinstance(body_field, Field)
|
||||
assert isinstance(body_field, ModelField)
|
||||
body_schema, _, _ = field_schema(
|
||||
body_field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
|
||||
)
|
||||
body_field.schema = cast(Body, body_field.schema)
|
||||
request_media_type = body_field.schema.media_type
|
||||
field_info = cast(Body, get_field_info(body_field))
|
||||
request_media_type = field_info.media_type
|
||||
required = body_field.required
|
||||
request_body_oai: Dict[str, Any] = {}
|
||||
if required:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
from enum import Enum
|
||||
from typing import Any, Callable, Sequence
|
||||
|
||||
from pydantic import Schema
|
||||
try:
|
||||
from pydantic.fields import FieldInfo
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic import Schema as FieldInfo # type: ignore
|
||||
|
||||
|
||||
class ParamTypes(Enum):
|
||||
|
|
@ -11,7 +15,7 @@ class ParamTypes(Enum):
|
|||
cookie = "cookie"
|
||||
|
||||
|
||||
class Param(Schema):
|
||||
class Param(FieldInfo):
|
||||
in_: ParamTypes
|
||||
|
||||
def __init__(
|
||||
|
|
@ -199,7 +203,7 @@ class Cookie(Param):
|
|||
)
|
||||
|
||||
|
||||
class Body(Schema):
|
||||
class Body(FieldInfo):
|
||||
def __init__(
|
||||
self,
|
||||
default: Any,
|
||||
|
|
|
|||
|
|
@ -14,10 +14,15 @@ from fastapi.dependencies.utils import (
|
|||
from fastapi.encoders import DictIntStrAny, SetIntStr, jsonable_encoder
|
||||
from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError
|
||||
from fastapi.openapi.constants import STATUS_CODES_WITH_NO_BODY
|
||||
from fastapi.utils import create_cloned_field, generate_operation_id_for_path
|
||||
from pydantic import BaseConfig, BaseModel, Schema
|
||||
from fastapi.utils import (
|
||||
PYDANTIC_1,
|
||||
create_cloned_field,
|
||||
generate_operation_id_for_path,
|
||||
get_field_info,
|
||||
warning_response_model_skip_defaults_deprecated,
|
||||
)
|
||||
from pydantic import BaseConfig, BaseModel
|
||||
from pydantic.error_wrappers import ErrorWrapper, ValidationError
|
||||
from pydantic.fields import Field
|
||||
from pydantic.utils import lenient_issubclass
|
||||
from starlette import routing
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
|
|
@ -34,20 +39,30 @@ from starlette.status import WS_1008_POLICY_VIOLATION
|
|||
from starlette.types import ASGIApp
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
try:
|
||||
from pydantic.fields import FieldInfo, ModelField
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic import Schema as FieldInfo # type: ignore
|
||||
from pydantic.fields import Field as ModelField # type: ignore
|
||||
|
||||
|
||||
def serialize_response(
|
||||
*,
|
||||
field: Field = None,
|
||||
field: ModelField = None,
|
||||
response: Response,
|
||||
include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
by_alias: bool = True,
|
||||
skip_defaults: bool = False,
|
||||
exclude_unset: bool = False,
|
||||
) -> Any:
|
||||
if field:
|
||||
errors = []
|
||||
if skip_defaults and isinstance(response, BaseModel):
|
||||
response = response.dict(skip_defaults=skip_defaults)
|
||||
if exclude_unset and isinstance(response, BaseModel):
|
||||
if PYDANTIC_1:
|
||||
response = response.dict(exclude_unset=exclude_unset)
|
||||
else:
|
||||
response = response.dict(skip_defaults=exclude_unset) # pragma: nocover
|
||||
value, errors_ = field.validate(response, {}, loc=("response",))
|
||||
if isinstance(errors_, ErrorWrapper):
|
||||
errors.append(errors_)
|
||||
|
|
@ -60,7 +75,7 @@ def serialize_response(
|
|||
include=include,
|
||||
exclude=exclude,
|
||||
by_alias=by_alias,
|
||||
skip_defaults=skip_defaults,
|
||||
exclude_unset=exclude_unset,
|
||||
)
|
||||
else:
|
||||
return jsonable_encoder(response)
|
||||
|
|
@ -68,19 +83,19 @@ def serialize_response(
|
|||
|
||||
def get_request_handler(
|
||||
dependant: Dependant,
|
||||
body_field: Field = None,
|
||||
body_field: ModelField = None,
|
||||
status_code: int = 200,
|
||||
response_class: Type[Response] = JSONResponse,
|
||||
response_field: Field = None,
|
||||
response_field: ModelField = None,
|
||||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_exclude_unset: bool = False,
|
||||
dependency_overrides_provider: Any = None,
|
||||
) -> Callable:
|
||||
assert dependant.call is not None, "dependant.call must be a function"
|
||||
is_coroutine = asyncio.iscoroutinefunction(dependant.call)
|
||||
is_body_form = body_field and isinstance(body_field.schema, params.Form)
|
||||
is_body_form = body_field and isinstance(get_field_info(body_field), params.Form)
|
||||
|
||||
async def app(request: Request) -> Response:
|
||||
try:
|
||||
|
|
@ -122,7 +137,7 @@ def get_request_handler(
|
|||
include=response_model_include,
|
||||
exclude=response_model_exclude,
|
||||
by_alias=response_model_by_alias,
|
||||
skip_defaults=response_model_skip_defaults,
|
||||
exclude_unset=response_model_exclude_unset,
|
||||
)
|
||||
response = response_class(
|
||||
content=response_data,
|
||||
|
|
@ -199,7 +214,7 @@ class APIRoute(routing.Route):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Optional[Type[Response]] = None,
|
||||
dependency_overrides_provider: Any = None,
|
||||
|
|
@ -220,15 +235,26 @@ class APIRoute(routing.Route):
|
|||
status_code not in STATUS_CODES_WITH_NO_BODY
|
||||
), f"Status code {status_code} must not have a response body"
|
||||
response_name = "Response_" + self.unique_id
|
||||
self.response_field: Optional[Field] = Field(
|
||||
name=response_name,
|
||||
type_=self.response_model,
|
||||
class_validators={},
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
schema=Schema(None),
|
||||
)
|
||||
if PYDANTIC_1:
|
||||
self.response_field: Optional[ModelField] = ModelField(
|
||||
name=response_name,
|
||||
type_=self.response_model,
|
||||
class_validators={},
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
field_info=FieldInfo(None),
|
||||
)
|
||||
else:
|
||||
self.response_field: Optional[ModelField] = ModelField( # type: ignore # pragma: nocover
|
||||
name=response_name,
|
||||
type_=self.response_model,
|
||||
class_validators={},
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
schema=FieldInfo(None),
|
||||
)
|
||||
# Create a clone of the field, so that a Pydantic submodel is not returned
|
||||
# as is just because it's an instance of a subclass of a more limited class
|
||||
# e.g. UserInDB (containing hashed_password) could be a subclass of User
|
||||
|
|
@ -236,9 +262,9 @@ class APIRoute(routing.Route):
|
|||
# would pass the validation and be returned as is.
|
||||
# By being a new field, no inheritance will be passed as is. A new model
|
||||
# will be always created.
|
||||
self.secure_cloned_response_field: Optional[Field] = create_cloned_field(
|
||||
self.response_field
|
||||
)
|
||||
self.secure_cloned_response_field: Optional[
|
||||
ModelField
|
||||
] = create_cloned_field(self.response_field)
|
||||
else:
|
||||
self.response_field = None
|
||||
self.secure_cloned_response_field = None
|
||||
|
|
@ -267,18 +293,29 @@ class APIRoute(routing.Route):
|
|||
model, BaseModel
|
||||
), "A response model must be a Pydantic model"
|
||||
response_name = f"Response_{additional_status_code}_{self.unique_id}"
|
||||
response_field = Field(
|
||||
name=response_name,
|
||||
type_=model,
|
||||
class_validators=None,
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
schema=Schema(None),
|
||||
)
|
||||
if PYDANTIC_1:
|
||||
response_field = ModelField(
|
||||
name=response_name,
|
||||
type_=model,
|
||||
class_validators=None,
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
field_info=FieldInfo(None),
|
||||
)
|
||||
else:
|
||||
response_field = ModelField( # type: ignore # pragma: nocover
|
||||
name=response_name,
|
||||
type_=model,
|
||||
class_validators=None,
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
schema=FieldInfo(None),
|
||||
)
|
||||
response_fields[additional_status_code] = response_field
|
||||
if response_fields:
|
||||
self.response_fields: Dict[Union[int, str], Field] = response_fields
|
||||
self.response_fields: Dict[Union[int, str], ModelField] = response_fields
|
||||
else:
|
||||
self.response_fields = {}
|
||||
self.deprecated = deprecated
|
||||
|
|
@ -286,7 +323,7 @@ class APIRoute(routing.Route):
|
|||
self.response_model_include = response_model_include
|
||||
self.response_model_exclude = response_model_exclude
|
||||
self.response_model_by_alias = response_model_by_alias
|
||||
self.response_model_skip_defaults = response_model_skip_defaults
|
||||
self.response_model_exclude_unset = response_model_exclude_unset
|
||||
self.include_in_schema = include_in_schema
|
||||
self.response_class = response_class
|
||||
|
||||
|
|
@ -313,7 +350,7 @@ class APIRoute(routing.Route):
|
|||
response_model_include=self.response_model_include,
|
||||
response_model_exclude=self.response_model_exclude,
|
||||
response_model_by_alias=self.response_model_by_alias,
|
||||
response_model_skip_defaults=self.response_model_skip_defaults,
|
||||
response_model_exclude_unset=self.response_model_exclude_unset,
|
||||
dependency_overrides_provider=self.dependency_overrides_provider,
|
||||
)
|
||||
|
||||
|
|
@ -352,12 +389,15 @@ class APIRouter(routing.Router):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
route_class_override: Optional[Type[APIRoute]] = None,
|
||||
) -> None:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
route_class = route_class_override or self.route_class
|
||||
route = route_class(
|
||||
path,
|
||||
|
|
@ -376,7 +416,9 @@ class APIRouter(routing.Router):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
|
|
@ -402,11 +444,15 @@ class APIRouter(routing.Router):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
|
||||
def decorator(func: Callable) -> Callable:
|
||||
self.add_api_route(
|
||||
path,
|
||||
|
|
@ -425,7 +471,9 @@ class APIRouter(routing.Router):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
|
|
@ -493,7 +541,7 @@ class APIRouter(routing.Router):
|
|||
response_model_include=route.response_model_include,
|
||||
response_model_exclude=route.response_model_exclude,
|
||||
response_model_by_alias=route.response_model_by_alias,
|
||||
response_model_skip_defaults=route.response_model_skip_defaults,
|
||||
response_model_exclude_unset=route.response_model_exclude_unset,
|
||||
include_in_schema=route.include_in_schema,
|
||||
response_class=route.response_class or default_response_class,
|
||||
name=route.name,
|
||||
|
|
@ -533,11 +581,14 @@ class APIRouter(routing.Router):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
|
|
@ -554,7 +605,9 @@ class APIRouter(routing.Router):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
|
|
@ -577,11 +630,14 @@ class APIRouter(routing.Router):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
|
|
@ -598,7 +654,9 @@ class APIRouter(routing.Router):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
|
|
@ -621,11 +679,14 @@ class APIRouter(routing.Router):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
|
|
@ -642,7 +703,9 @@ class APIRouter(routing.Router):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
|
|
@ -665,11 +728,14 @@ class APIRouter(routing.Router):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
|
|
@ -686,7 +752,9 @@ class APIRouter(routing.Router):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
|
|
@ -709,11 +777,14 @@ class APIRouter(routing.Router):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
|
|
@ -730,7 +801,9 @@ class APIRouter(routing.Router):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
|
|
@ -753,11 +826,14 @@ class APIRouter(routing.Router):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
|
|
@ -774,7 +850,9 @@ class APIRouter(routing.Router):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
|
|
@ -797,11 +875,14 @@ class APIRouter(routing.Router):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
|
|
@ -818,7 +899,9 @@ class APIRouter(routing.Router):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
|
|
@ -841,11 +924,14 @@ class APIRouter(routing.Router):
|
|||
response_model_include: Union[SetIntStr, DictIntStrAny] = None,
|
||||
response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
response_model_by_alias: bool = True,
|
||||
response_model_skip_defaults: bool = False,
|
||||
response_model_skip_defaults: bool = None,
|
||||
response_model_exclude_unset: bool = False,
|
||||
include_in_schema: bool = True,
|
||||
response_class: Type[Response] = None,
|
||||
name: str = None,
|
||||
) -> Callable:
|
||||
if response_model_skip_defaults is not None:
|
||||
warning_response_model_skip_defaults_deprecated() # pragma: nocover
|
||||
return self.api_route(
|
||||
path=path,
|
||||
response_model=response_model,
|
||||
|
|
@ -862,7 +948,9 @@ class APIRouter(routing.Router):
|
|||
response_model_include=response_model_include,
|
||||
response_model_exclude=response_model_exclude,
|
||||
response_model_by_alias=response_model_by_alias,
|
||||
response_model_skip_defaults=response_model_skip_defaults,
|
||||
response_model_exclude_unset=bool(
|
||||
response_model_exclude_unset or response_model_skip_defaults
|
||||
),
|
||||
include_in_schema=include_in_schema,
|
||||
response_class=response_class,
|
||||
name=name,
|
||||
|
|
|
|||
|
|
@ -1,26 +1,60 @@
|
|||
import logging
|
||||
import re
|
||||
from dataclasses import is_dataclass
|
||||
from typing import Any, Dict, List, Sequence, Set, Type, cast
|
||||
|
||||
from fastapi import routing
|
||||
from fastapi.openapi.constants import REF_PREFIX
|
||||
from pydantic import BaseConfig, BaseModel, Schema, create_model
|
||||
from pydantic.fields import Field
|
||||
from pydantic import BaseConfig, BaseModel, create_model
|
||||
from pydantic.schema import get_flat_models_from_fields, model_process_schema
|
||||
from pydantic.utils import lenient_issubclass
|
||||
from starlette.routing import BaseRoute
|
||||
|
||||
logger = logging.getLogger("fastapi")
|
||||
|
||||
try:
|
||||
from pydantic.fields import FieldInfo, ModelField
|
||||
|
||||
PYDANTIC_1 = True
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic.fields import Field as ModelField # type: ignore
|
||||
from pydantic import Schema as FieldInfo # type: ignore
|
||||
|
||||
logger.warning(
|
||||
"Pydantic versions < 1.0.0 are deprecated in FastAPI and support will be \
|
||||
removed soon"
|
||||
)
|
||||
PYDANTIC_1 = False
|
||||
|
||||
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
def get_field_info(field: ModelField) -> FieldInfo:
|
||||
if PYDANTIC_1:
|
||||
return field.field_info # type: ignore
|
||||
else:
|
||||
return field.schema # type: ignore # pragma: nocover
|
||||
|
||||
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
def warning_response_model_skip_defaults_deprecated() -> None:
|
||||
logger.warning( # pragma: nocover
|
||||
"response_model_skip_defaults has been deprecated in favor \
|
||||
of response_model_exclude_unset to keep in line with Pydantic v1, \
|
||||
support for it will be removed soon."
|
||||
)
|
||||
|
||||
|
||||
def get_flat_models_from_routes(routes: Sequence[BaseRoute]) -> Set[Type[BaseModel]]:
|
||||
body_fields_from_routes: List[Field] = []
|
||||
responses_from_routes: List[Field] = []
|
||||
body_fields_from_routes: List[ModelField] = []
|
||||
responses_from_routes: List[ModelField] = []
|
||||
for route in routes:
|
||||
if getattr(route, "include_in_schema", None) and isinstance(
|
||||
route, routing.APIRoute
|
||||
):
|
||||
if route.body_field:
|
||||
assert isinstance(
|
||||
route.body_field, Field
|
||||
route.body_field, ModelField
|
||||
), "A request body must be a Pydantic Field"
|
||||
body_fields_from_routes.append(route.body_field)
|
||||
if route.response_field:
|
||||
|
|
@ -51,7 +85,7 @@ def get_path_param_names(path: str) -> Set[str]:
|
|||
return {item.strip("{}") for item in re.findall("{[^}]*}", path)}
|
||||
|
||||
|
||||
def create_cloned_field(field: Field) -> Field:
|
||||
def create_cloned_field(field: ModelField) -> ModelField:
|
||||
original_type = field.type_
|
||||
if is_dataclass(original_type) and hasattr(original_type, "__pydantic_model__"):
|
||||
original_type = original_type.__pydantic_model__ # type: ignore
|
||||
|
|
@ -64,22 +98,36 @@ def create_cloned_field(field: Field) -> Field:
|
|||
for f in original_type.__fields__.values():
|
||||
use_type.__fields__[f.name] = f
|
||||
use_type.__validators__ = original_type.__validators__
|
||||
new_field = Field(
|
||||
name=field.name,
|
||||
type_=use_type,
|
||||
class_validators={},
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
schema=Schema(None),
|
||||
)
|
||||
if PYDANTIC_1:
|
||||
new_field = ModelField(
|
||||
name=field.name,
|
||||
type_=use_type,
|
||||
class_validators={},
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
field_info=FieldInfo(None),
|
||||
)
|
||||
else: # pragma: nocover
|
||||
new_field = ModelField( # type: ignore
|
||||
name=field.name,
|
||||
type_=use_type,
|
||||
class_validators={},
|
||||
default=None,
|
||||
required=False,
|
||||
model_config=BaseConfig,
|
||||
schema=FieldInfo(None),
|
||||
)
|
||||
new_field.has_alias = field.has_alias
|
||||
new_field.alias = field.alias
|
||||
new_field.class_validators = field.class_validators
|
||||
new_field.default = field.default
|
||||
new_field.required = field.required
|
||||
new_field.model_config = field.model_config
|
||||
new_field.schema = field.schema
|
||||
if PYDANTIC_1:
|
||||
new_field.field_info = field.field_info
|
||||
else: # pragma: nocover
|
||||
new_field.schema = field.schema # type: ignore
|
||||
new_field.allow_none = field.allow_none
|
||||
new_field.validate_always = field.validate_always
|
||||
if field.sub_fields:
|
||||
|
|
@ -89,11 +137,19 @@ def create_cloned_field(field: Field) -> Field:
|
|||
if field.key_field:
|
||||
new_field.key_field = create_cloned_field(field.key_field)
|
||||
new_field.validators = field.validators
|
||||
new_field.whole_pre_validators = field.whole_pre_validators
|
||||
new_field.whole_post_validators = field.whole_post_validators
|
||||
if PYDANTIC_1:
|
||||
new_field.pre_validators = field.pre_validators
|
||||
new_field.post_validators = field.post_validators
|
||||
else: # pragma: nocover
|
||||
new_field.whole_pre_validators = field.whole_pre_validators # type: ignore
|
||||
new_field.whole_post_validators = field.whole_post_validators # type: ignore
|
||||
new_field.parse_json = field.parse_json
|
||||
new_field.shape = field.shape
|
||||
new_field._populate_validators()
|
||||
try:
|
||||
new_field.populate_validators()
|
||||
except AttributeError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
new_field._populate_validators() # type: ignore
|
||||
return new_field
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ classifiers = [
|
|||
]
|
||||
requires = [
|
||||
"starlette >=0.12.9,<=0.12.9",
|
||||
"pydantic >=0.32.2,<=0.32.2"
|
||||
"pydantic >=0.32.2,<2.0.0"
|
||||
]
|
||||
description-file = "README.md"
|
||||
requires-python = ">=3.6"
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id"},
|
||||
"schema": {"title": "Item Id"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -98,7 +98,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -128,7 +128,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "integer"},
|
||||
"schema": {"title": "Item Id", "type": "integer"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -158,7 +158,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "number"},
|
||||
"schema": {"title": "Item Id", "type": "number"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -188,7 +188,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "boolean"},
|
||||
"schema": {"title": "Item Id", "type": "boolean"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -218,7 +218,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -248,7 +248,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -279,7 +279,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"minLength": 3,
|
||||
"type": "string",
|
||||
},
|
||||
|
|
@ -313,7 +313,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"maxLength": 3,
|
||||
"type": "string",
|
||||
},
|
||||
|
|
@ -347,7 +347,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"maxLength": 3,
|
||||
"minLength": 2,
|
||||
"type": "string",
|
||||
|
|
@ -382,7 +382,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"exclusiveMinimum": 3.0,
|
||||
"type": "number",
|
||||
},
|
||||
|
|
@ -416,7 +416,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"exclusiveMinimum": 0.0,
|
||||
"type": "number",
|
||||
},
|
||||
|
|
@ -450,7 +450,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"minimum": 3.0,
|
||||
"type": "number",
|
||||
},
|
||||
|
|
@ -484,7 +484,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"exclusiveMaximum": 3.0,
|
||||
"type": "number",
|
||||
},
|
||||
|
|
@ -518,7 +518,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"exclusiveMaximum": 0.0,
|
||||
"type": "number",
|
||||
},
|
||||
|
|
@ -552,7 +552,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"maximum": 3.0,
|
||||
"type": "number",
|
||||
},
|
||||
|
|
@ -586,7 +586,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"exclusiveMaximum": 3.0,
|
||||
"exclusiveMinimum": 1.0,
|
||||
"type": "number",
|
||||
|
|
@ -621,7 +621,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"maximum": 3.0,
|
||||
"minimum": 1.0,
|
||||
"type": "number",
|
||||
|
|
@ -656,7 +656,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"exclusiveMaximum": 3.0,
|
||||
"type": "integer",
|
||||
},
|
||||
|
|
@ -690,7 +690,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"exclusiveMinimum": 3.0,
|
||||
"type": "integer",
|
||||
},
|
||||
|
|
@ -724,7 +724,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"maximum": 3.0,
|
||||
"type": "integer",
|
||||
},
|
||||
|
|
@ -758,7 +758,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"minimum": 3.0,
|
||||
"type": "integer",
|
||||
},
|
||||
|
|
@ -792,7 +792,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"exclusiveMaximum": 3.0,
|
||||
"exclusiveMinimum": 1.0,
|
||||
"type": "integer",
|
||||
|
|
@ -827,7 +827,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"maximum": 3.0,
|
||||
"minimum": 1.0,
|
||||
"type": "integer",
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -141,7 +141,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -169,7 +169,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -197,7 +197,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -233,7 +233,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -263,7 +263,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ def test_schema_1():
|
|||
|
||||
d = {
|
||||
"required": True,
|
||||
"schema": {"title": "User_Id", "type": "string"},
|
||||
"schema": {"title": "User Id", "type": "string"},
|
||||
"name": "user_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -127,7 +127,7 @@ def test_schema_2():
|
|||
|
||||
d = {
|
||||
"required": False,
|
||||
"schema": {"title": "User_Id", "type": "string"},
|
||||
"schema": {"title": "User Id", "type": "string"},
|
||||
"name": "user_id",
|
||||
"in": "query",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,13 @@ from enum import Enum
|
|||
|
||||
import pytest
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from pydantic import BaseModel, Schema, ValidationError
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
try:
|
||||
from pydantic import Field
|
||||
except ImportError: # pragma: nocover
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
from pydantic import Schema as Field
|
||||
|
||||
|
||||
class Person:
|
||||
|
|
@ -60,7 +66,7 @@ class ModelWithConfig(BaseModel):
|
|||
|
||||
|
||||
class ModelWithAlias(BaseModel):
|
||||
foo: str = Schema(..., alias="Foo")
|
||||
foo: str = Field(..., alias="Foo")
|
||||
|
||||
|
||||
def test_encode_class():
|
||||
|
|
|
|||
|
|
@ -18,6 +18,16 @@ def test_nonexistent():
|
|||
assert response.json() == {"detail": "Not Found"}
|
||||
|
||||
|
||||
response_not_valid_bool = {
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["path", "item_id"],
|
||||
"msg": "value could not be parsed to a boolean",
|
||||
"type": "type_error.bool",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
response_not_valid_int = {
|
||||
"detail": [
|
||||
{
|
||||
|
|
@ -173,10 +183,10 @@ response_less_than_equal_3 = {
|
|||
("/path/float/True", 422, response_not_valid_float),
|
||||
("/path/float/42", 200, 42),
|
||||
("/path/float/42.5", 200, 42.5),
|
||||
("/path/bool/foobar", 200, False),
|
||||
("/path/bool/foobar", 422, response_not_valid_bool),
|
||||
("/path/bool/True", 200, True),
|
||||
("/path/bool/42", 200, False),
|
||||
("/path/bool/42.5", 200, False),
|
||||
("/path/bool/42", 422, response_not_valid_bool),
|
||||
("/path/bool/42.5", 422, response_not_valid_bool),
|
||||
("/path/bool/1", 200, True),
|
||||
("/path/bool/0", 200, False),
|
||||
("/path/bool/true", 200, True),
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,15 +99,15 @@ openapi_schema = {
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"grant_type": {
|
||||
"title": "Grant_Type",
|
||||
"title": "Grant Type",
|
||||
"pattern": "password",
|
||||
"type": "string",
|
||||
},
|
||||
"username": {"title": "Username", "type": "string"},
|
||||
"password": {"title": "Password", "type": "string"},
|
||||
"scope": {"title": "Scope", "type": "string", "default": ""},
|
||||
"client_id": {"title": "Client_Id", "type": "string"},
|
||||
"client_secret": {"title": "Client_Secret", "type": "string"},
|
||||
"client_id": {"title": "Client Id", "type": "string"},
|
||||
"client_secret": {"title": "Client Secret", "type": "string"},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
|
|
|
|||
|
|
@ -103,15 +103,15 @@ openapi_schema = {
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"grant_type": {
|
||||
"title": "Grant_Type",
|
||||
"title": "Grant Type",
|
||||
"pattern": "password",
|
||||
"type": "string",
|
||||
},
|
||||
"username": {"title": "Username", "type": "string"},
|
||||
"password": {"title": "Password", "type": "string"},
|
||||
"scope": {"title": "Scope", "type": "string", "default": ""},
|
||||
"client_id": {"title": "Client_Id", "type": "string"},
|
||||
"client_secret": {"title": "Client_Secret", "type": "string"},
|
||||
"client_id": {"title": "Client Id", "type": "string"},
|
||||
"client_secret": {"title": "Client Secret", "type": "string"},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class ModelSubclass(Model):
|
|||
y: int
|
||||
|
||||
|
||||
@app.get("/", response_model=Model, response_model_skip_defaults=True)
|
||||
@app.get("/", response_model=Model, response_model_exclude_unset=True)
|
||||
def get() -> ModelSubclass:
|
||||
return ModelSubclass(sub={}, y=1)
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -84,7 +84,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ openapi_schema = {
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response_Read_Notes_Notes__Get",
|
||||
"title": "Response Read Notes Notes Get",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/Note"},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
},
|
||||
|
|
@ -160,7 +160,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "integer"},
|
||||
"schema": {"title": "Item Id", "type": "integer"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ openapi_schema = {
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"username": {"title": "Username", "type": "string"},
|
||||
"full_name": {"title": "Full_Name", "type": "string"},
|
||||
"full_name": {"title": "Full Name", "type": "string"},
|
||||
},
|
||||
},
|
||||
"Body_update_item_items__item_id__put": {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ from starlette.testclient import TestClient
|
|||
|
||||
from body_schema.tutorial001 import app
|
||||
|
||||
# TODO: remove when removing support for Pydantic < 1.0.0
|
||||
try:
|
||||
from pydantic import Field # noqa
|
||||
except ImportError: # pragma: nocover
|
||||
import pydantic
|
||||
|
||||
pydantic.Field = pydantic.Schema
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
|
|
@ -33,7 +42,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "integer"},
|
||||
"schema": {"title": "Item Id", "type": "integer"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": False,
|
||||
"schema": {"title": "Ads_Id", "type": "string"},
|
||||
"schema": {"title": "Ads Id", "type": "string"},
|
||||
"name": "ads_id",
|
||||
"in": "cookie",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Item_Id",
|
||||
"title": "Item Id",
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
},
|
||||
|
|
@ -60,22 +60,22 @@ openapi_schema = {
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"start_datetime": {
|
||||
"title": "Start_Datetime",
|
||||
"title": "Start Datetime",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
},
|
||||
"end_datetime": {
|
||||
"title": "End_Datetime",
|
||||
"title": "End Datetime",
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
},
|
||||
"repeat_at": {
|
||||
"title": "Repeat_At",
|
||||
"title": "Repeat At",
|
||||
"type": "string",
|
||||
"format": "time",
|
||||
},
|
||||
"process_after": {
|
||||
"title": "Process_After",
|
||||
"title": "Process After",
|
||||
"type": "number",
|
||||
"format": "time-delta",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ openapi_schema = {
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response_Read_Item_Items__Item_Id__Get",
|
||||
"title": "Response Read Item Items Item Id Get",
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/PlaneItem"},
|
||||
{"$ref": "#/components/schemas/CarItem"},
|
||||
|
|
@ -41,7 +41,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ openapi_schema = {
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response_Read_Items_Items__Get",
|
||||
"title": "Response Read Items Items Get",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/Item"},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ openapi_schema = {
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response_Read_Keyword_Weights_Keyword-Weights__Get",
|
||||
"title": "Response Read Keyword Weights Keyword-Weights Get",
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "number"},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "integer"},
|
||||
"schema": {"title": "Item Id", "type": "integer"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "integer"},
|
||||
"schema": {"title": "Item Id", "type": "integer"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "File_Path", "type": "string"},
|
||||
"schema": {"title": "File Path", "type": "string"},
|
||||
"name": "file_path",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ openapi_schema = {
|
|||
{
|
||||
"required": True,
|
||||
"schema": {
|
||||
"title": "Model_Name",
|
||||
"title": "Model Name",
|
||||
"enum": ["alexnet", "resnet", "lenet"],
|
||||
"type": "string",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ openapi_schema = {
|
|||
"properties": {
|
||||
"username": {"title": "Username", "type": "string"},
|
||||
"email": {"title": "Email", "type": "string", "format": "email"},
|
||||
"full_name": {"title": "Full_Name", "type": "string"},
|
||||
"full_name": {"title": "Full Name", "type": "string"},
|
||||
},
|
||||
},
|
||||
"UserIn": {
|
||||
|
|
@ -63,7 +63,7 @@ openapi_schema = {
|
|||
"username": {"title": "Username", "type": "string"},
|
||||
"password": {"title": "Password", "type": "string"},
|
||||
"email": {"title": "Email", "type": "string", "format": "email"},
|
||||
"full_name": {"title": "Full_Name", "type": "string"},
|
||||
"full_name": {"title": "Full Name", "type": "string"},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"schema": {"title": "Item Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,15 +62,15 @@ openapi_schema = {
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"grant_type": {
|
||||
"title": "Grant_Type",
|
||||
"title": "Grant Type",
|
||||
"pattern": "password",
|
||||
"type": "string",
|
||||
},
|
||||
"username": {"title": "Username", "type": "string"},
|
||||
"password": {"title": "Password", "type": "string"},
|
||||
"scope": {"title": "Scope", "type": "string", "default": ""},
|
||||
"client_id": {"title": "Client_Id", "type": "string"},
|
||||
"client_secret": {"title": "Client_Secret", "type": "string"},
|
||||
"client_id": {"title": "Client Id", "type": "string"},
|
||||
"client_secret": {"title": "Client Secret", "type": "string"},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ openapi_schema = {
|
|||
"properties": {
|
||||
"username": {"title": "Username", "type": "string"},
|
||||
"email": {"title": "Email", "type": "string"},
|
||||
"full_name": {"title": "Full_Name", "type": "string"},
|
||||
"full_name": {"title": "Full Name", "type": "string"},
|
||||
"disabled": {"title": "Disabled", "type": "boolean"},
|
||||
},
|
||||
},
|
||||
|
|
@ -112,8 +112,8 @@ openapi_schema = {
|
|||
"required": ["access_token", "token_type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"access_token": {"title": "Access_Token", "type": "string"},
|
||||
"token_type": {"title": "Token_Type", "type": "string"},
|
||||
"access_token": {"title": "Access Token", "type": "string"},
|
||||
"token_type": {"title": "Token Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"Body_login_for_access_token_token_post": {
|
||||
|
|
@ -122,15 +122,15 @@ openapi_schema = {
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"grant_type": {
|
||||
"title": "Grant_Type",
|
||||
"title": "Grant Type",
|
||||
"pattern": "password",
|
||||
"type": "string",
|
||||
},
|
||||
"username": {"title": "Username", "type": "string"},
|
||||
"password": {"title": "Password", "type": "string"},
|
||||
"scope": {"title": "Scope", "type": "string", "default": ""},
|
||||
"client_id": {"title": "Client_Id", "type": "string"},
|
||||
"client_secret": {"title": "Client_Secret", "type": "string"},
|
||||
"client_id": {"title": "Client Id", "type": "string"},
|
||||
"client_secret": {"title": "Client Secret", "type": "string"},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ openapi_schema = {
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response_Read_Users_Users__Get",
|
||||
"title": "Response Read Users Users Get",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/User"},
|
||||
}
|
||||
|
|
@ -110,7 +110,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "User_Id", "type": "integer"},
|
||||
"schema": {"title": "User Id", "type": "integer"},
|
||||
"name": "user_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -144,7 +144,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "User_Id", "type": "integer"},
|
||||
"schema": {"title": "User Id", "type": "integer"},
|
||||
"name": "user_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -167,7 +167,7 @@ openapi_schema = {
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response_Read_Items_Items__Get",
|
||||
"title": "Response Read Items Items Get",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/Item"},
|
||||
}
|
||||
|
|
@ -223,7 +223,7 @@ openapi_schema = {
|
|||
"title": {"title": "Title", "type": "string"},
|
||||
"description": {"title": "Description", "type": "string"},
|
||||
"id": {"title": "Id", "type": "integer"},
|
||||
"owner_id": {"title": "Owner_Id", "type": "integer"},
|
||||
"owner_id": {"title": "Owner Id", "type": "integer"},
|
||||
},
|
||||
},
|
||||
"User": {
|
||||
|
|
@ -233,7 +233,7 @@ openapi_schema = {
|
|||
"properties": {
|
||||
"email": {"title": "Email", "type": "string"},
|
||||
"id": {"title": "Id", "type": "integer"},
|
||||
"is_active": {"title": "Is_Active", "type": "boolean"},
|
||||
"is_active": {"title": "Is Active", "type": "boolean"},
|
||||
"items": {
|
||||
"title": "Items",
|
||||
"type": "array",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ openapi_schema = {
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response_Read_Users_Users__Get",
|
||||
"title": "Response Read Users Users Get",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/User"},
|
||||
}
|
||||
|
|
@ -110,7 +110,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "User_Id", "type": "integer"},
|
||||
"schema": {"title": "User Id", "type": "integer"},
|
||||
"name": "user_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -144,7 +144,7 @@ openapi_schema = {
|
|||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "User_Id", "type": "integer"},
|
||||
"schema": {"title": "User Id", "type": "integer"},
|
||||
"name": "user_id",
|
||||
"in": "path",
|
||||
}
|
||||
|
|
@ -167,7 +167,7 @@ openapi_schema = {
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response_Read_Items_Items__Get",
|
||||
"title": "Response Read Items Items Get",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/Item"},
|
||||
}
|
||||
|
|
@ -223,7 +223,7 @@ openapi_schema = {
|
|||
"title": {"title": "Title", "type": "string"},
|
||||
"description": {"title": "Description", "type": "string"},
|
||||
"id": {"title": "Id", "type": "integer"},
|
||||
"owner_id": {"title": "Owner_Id", "type": "integer"},
|
||||
"owner_id": {"title": "Owner Id", "type": "integer"},
|
||||
},
|
||||
},
|
||||
"User": {
|
||||
|
|
@ -233,7 +233,7 @@ openapi_schema = {
|
|||
"properties": {
|
||||
"email": {"title": "Email", "type": "string"},
|
||||
"id": {"title": "Id", "type": "integer"},
|
||||
"is_active": {"title": "Is_Active", "type": "boolean"},
|
||||
"is_active": {"title": "Is Active", "type": "boolean"},
|
||||
"items": {
|
||||
"title": "Items",
|
||||
"type": "array",
|
||||
|
|
|
|||
Loading…
Reference in New Issue