From ab2b86fe2ce8fe15e91aaec179438e24ff7b7ed0 Mon Sep 17 00:00:00 2001
From: dmontagu <35119617+dmontagu@users.noreply.github.com>
Date: Wed, 27 Nov 2019 11:32:02 -0800
Subject: [PATCH] :sparkles: Add support for Pydantic v1 and above :tada:
(#646)
* Make compatible with pydantic v1
* Remove unused import
* Remove unused ignores
* Update pydantic version
* Fix minor formatting issue
* :rewind: Revert removing iterate_in_threadpool
* :sparkles: Add backwards compatibility with Pydantic 0.32.2
with deprecation warnings
* :white_check_mark: Update tests to not break when using Pydantic < 1.0.0
* :memo: Update docs for Pydantic version 1.0.0
* :pushpin: Update Pydantic range version to support from 0.32.2
* :art: Format test imports
* :sparkles: Add support for Pydantic < 1.2 for populate_validators
* :sparkles: Add backwards compatibility for Pydantic < 1.2.0 with required fields
* :pushpin: Relax requirement for Pydantic to < 2.0.0 :tada: :rocket:
* :green_heart: Update pragma coverage for older Pydantic versions
---
Pipfile | 2 +-
docs/src/body_schema/tutorial001.py | 6 +-
docs/src/body_updates/tutorial002.py | 2 +-
docs/src/extra_models/tutorial001.py | 3 +-
docs/src/extra_models/tutorial002.py | 3 +-
docs/src/response_model/tutorial002.py | 3 +-
docs/src/response_model/tutorial003.py | 3 +-
docs/src/response_model/tutorial004.py | 2 +-
docs/tutorial/body-schema.md | 26 +-
docs/tutorial/body-updates.md | 10 +-
docs/tutorial/extra-models.md | 4 +-
docs/tutorial/response-model.md | 20 +-
fastapi/applications.py | 92 +++--
fastapi/concurrency.py | 3 +-
fastapi/dependencies/models.py | 17 +-
fastapi/dependencies/utils.py | 317 ++++++++++++------
fastapi/encoders.py | 35 +-
fastapi/exceptions.py | 17 +-
fastapi/openapi/models.py | 74 ++--
fastapi/openapi/utils.py | 35 +-
fastapi/params.py | 10 +-
fastapi/routing.py | 206 ++++++++----
fastapi/utils.py | 94 ++++--
pyproject.toml | 2 +-
tests/test_application.py | 48 +--
tests/test_extra_routes.py | 14 +-
tests/test_infer_param_optionality.py | 4 +-
tests/test_jsonable_encoder.py | 10 +-
tests/test_path.py | 16 +-
tests/test_put_no_body.py | 2 +-
tests/test_security_oauth2.py | 6 +-
tests/test_security_oauth2_optional.py | 6 +-
tests/test_skip_defaults.py | 2 +-
tests/test_starlette_exception.py | 4 +-
.../test_tutorial001.py | 2 +-
.../test_tutorial002.py | 2 +-
.../test_tutorial003.py | 2 +-
.../test_tutorial004.py | 2 +-
.../test_tutorial001.py | 2 +-
.../test_bigger_applications/test_main.py | 4 +-
.../test_tutorial003.py | 4 +-
.../test_body_schema/test_tutorial001.py | 11 +-
.../test_body_updates/test_tutorial001.py | 4 +-
.../test_cookie_params/test_tutorial001.py | 2 +-
.../test_events/test_tutorial001.py | 2 +-
.../test_extra_data_types/test_tutorial001.py | 10 +-
.../test_extra_models/test_tutorial003.py | 4 +-
.../test_extra_models/test_tutorial004.py | 2 +-
.../test_extra_models/test_tutorial005.py | 2 +-
.../test_handling_errors/test_tutorial001.py | 2 +-
.../test_handling_errors/test_tutorial002.py | 2 +-
.../test_handling_errors/test_tutorial004.py | 2 +-
.../test_handling_errors/test_tutorial005.py | 2 +-
.../test_path_params/test_tutorial004.py | 2 +-
.../test_path_params/test_tutorial005.py | 2 +-
.../test_query_params/test_tutorial005.py | 2 +-
.../test_query_params/test_tutorial006.py | 2 +-
.../test_query_params/test_tutorial007.py | 2 +-
.../test_response_model/test_tutorial003.py | 4 +-
.../test_response_model/test_tutorial004.py | 2 +-
.../test_response_model/test_tutorial005.py | 4 +-
.../test_response_model/test_tutorial006.py | 4 +-
.../test_security/test_tutorial003.py | 6 +-
.../test_security/test_tutorial005.py | 12 +-
.../test_sql_databases/test_sql_databases.py | 12 +-
.../test_sql_databases_middleware.py | 12 +-
66 files changed, 802 insertions(+), 425 deletions(-)
diff --git a/Pipfile b/Pipfile
index 9b8d31bea80d5..4f1ba44d8b2a4 100644
--- a/Pipfile
+++ b/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 = "*"
diff --git a/docs/src/body_schema/tutorial001.py b/docs/src/body_schema/tutorial001.py
index 6c8b101ba220a..7b2c9454d82f4 100644
--- a/docs/src/body_schema/tutorial001.py
+++ b/docs/src/body_schema/tutorial001.py
@@ -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
diff --git a/docs/src/body_updates/tutorial002.py b/docs/src/body_updates/tutorial002.py
index e10fbb9213cdd..56cb8c4dce663 100644
--- a/docs/src/body_updates/tutorial002.py
+++ b/docs/src/body_updates/tutorial002.py
@@ -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
diff --git a/docs/src/extra_models/tutorial001.py b/docs/src/extra_models/tutorial001.py
index aa8e7dad456b1..08d3659b0148d 100644
--- a/docs/src/extra_models/tutorial001.py
+++ b/docs/src/extra_models/tutorial001.py
@@ -1,6 +1,5 @@
from fastapi import FastAPI
-from pydantic import BaseModel
-from pydantic.types import EmailStr
+from pydantic import BaseModel, EmailStr
app = FastAPI()
diff --git a/docs/src/extra_models/tutorial002.py b/docs/src/extra_models/tutorial002.py
index 605baf91f05e9..ab680eca0adc3 100644
--- a/docs/src/extra_models/tutorial002.py
+++ b/docs/src/extra_models/tutorial002.py
@@ -1,6 +1,5 @@
from fastapi import FastAPI
-from pydantic import BaseModel
-from pydantic.types import EmailStr
+from pydantic import BaseModel, EmailStr
app = FastAPI()
diff --git a/docs/src/response_model/tutorial002.py b/docs/src/response_model/tutorial002.py
index 3fb475b9d9cc7..b36b2a347cdbd 100644
--- a/docs/src/response_model/tutorial002.py
+++ b/docs/src/response_model/tutorial002.py
@@ -1,6 +1,5 @@
from fastapi import FastAPI
-from pydantic import BaseModel
-from pydantic.types import EmailStr
+from pydantic import BaseModel, EmailStr
app = FastAPI()
diff --git a/docs/src/response_model/tutorial003.py b/docs/src/response_model/tutorial003.py
index c8ea361d8d45b..a73372901416d 100644
--- a/docs/src/response_model/tutorial003.py
+++ b/docs/src/response_model/tutorial003.py
@@ -1,6 +1,5 @@
from fastapi import FastAPI
-from pydantic import BaseModel
-from pydantic.types import EmailStr
+from pydantic import BaseModel, EmailStr
app = FastAPI()
diff --git a/docs/src/response_model/tutorial004.py b/docs/src/response_model/tutorial004.py
index bbfd855bbd7ea..7a2373e0cf61f 100644
--- a/docs/src/response_model/tutorial004.py
+++ b/docs/src/response_model/tutorial004.py
@@ -21,6 +21,6 @@ class Item(BaseModel):
}
-@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]
diff --git a/docs/tutorial/body-schema.md b/docs/tutorial/body-schema.md
index 45ea5d93d4022..9f0a493701b09 100644
--- a/docs/tutorial/body-schema.md
+++ b/docs/tutorial/body-schema.md
@@ -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`, those are actually functions that return classes of the same name.
+ `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`, those are actually functions that return classes of the same name.
!!! 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.
\ No newline at end of file
+You can also use the extra keyword arguments to pass additional JSON Schema metadata.
diff --git a/docs/tutorial/body-updates.md b/docs/tutorial/body-updates.md
index 0850d5d44089b..33880aad68af5 100644
--- a/docs/tutorial/body-updates.md
+++ b/docs/tutorial/body-updates.md
@@ -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`).
diff --git a/docs/tutorial/extra-models.md b/docs/tutorial/extra-models.md
index 5d301f64cf221..875ce8aec1e4c 100644
--- a/docs/tutorial/extra-models.md
+++ b/docs/tutorial/extra-models.md
@@ -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!}
```
diff --git a/docs/tutorial/response-model.md b/docs/tutorial/response-model.md
index 00c9b987c4a4a..8057bd23b3fa8 100644
--- a/docs/tutorial/response-model.md
+++ b/docs/tutorial/response-model.md
@@ -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 its `skip_defaults` parameter to achieve this.
+ FastAPI uses Pydantic model's `.dict()` with its `exclude_unset` parameter 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.
diff --git a/fastapi/applications.py b/fastapi/applications.py
index 000026b8384fc..ab1b77e6dcc4b 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -15,6 +15,7 @@
)
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 @@ def add_api_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_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 @@ def add_api_route(
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 @@ def api_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_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 @@ def decorator(func: Callable) -> Callable:
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 @@ def get(
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 @@ def get(
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 @@ def put(
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 @@ def put(
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 @@ def post(
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 @@ def post(
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 @@ def delete(
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 @@ def delete(
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 @@ def options(
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 @@ def options(
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 @@ def head(
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 @@ def head(
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 @@ def patch(
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 @@ def patch(
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 @@ def trace(
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 @@ def trace(
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,
diff --git a/fastapi/concurrency.py b/fastapi/concurrency.py
index 7006c1ad0c2ff..03ed2d08d489a 100644
--- a/fastapi/concurrency.py
+++ b/fastapi/concurrency.py
@@ -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,
diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py
index 12f0679ed76cb..5fe99f3b46ce3 100644
--- a/fastapi/dependencies/models.py
+++ b/fastapi/dependencies/models.py
@@ -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,
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index 4745f173f0d6e..54e27476249d9 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -27,13 +27,11 @@
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.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
diff --git a/fastapi/encoders.py b/fastapi/encoders.py
index 0f4fd8346aab4..bececedae8f44 100644
--- a/fastapi/encoders.py
+++ b/fastapi/encoders.py
@@ -2,6 +2,7 @@
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,
+ 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=skip_defaults,
- ),
+ 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,
diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py
index 310da940d8e8f..d4b1329d7d890 100644
--- a/fastapi/exceptions.py
+++ b/fastapi/exceptions.py
@@ -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 @@ def __init__(
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
diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py
index e5c50070e04b6..126b2dfeb8cd5 100644
--- a/fastapi/openapi/models.py
+++ b/fastapi/openapi/models.py
@@ -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 @@ class EmailStr(str): # type: ignore
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
diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py
index 311fb25a733f3..d2dd620813e41 100644
--- a/fastapi/openapi/utils.py
+++ b/fastapi/openapi/utils.py
@@ -14,16 +14,23 @@
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 @@
}
-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:
diff --git a/fastapi/params.py b/fastapi/params.py
index 0541a3695839c..3aa333ac75615 100644
--- a/fastapi/params.py
+++ b/fastapi/params.py
@@ -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 @@ def __init__(
)
-class Body(Schema):
+class Body(FieldInfo):
def __init__(
self,
default: Any,
diff --git a/fastapi/routing.py b/fastapi/routing.py
index b2a900b7e0102..547d2c4b140d1 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -14,10 +14,15 @@
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.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 @@ async def app(request: Request) -> Response:
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 @@ def __init__(
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 @@ def __init__(
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 @@ def __init__(
# 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 @@ def __init__(
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 @@ def __init__(
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 @@ def get_route_handler(self) -> Callable:
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 @@ def add_api_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_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 @@ def add_api_route(
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 @@ def api_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_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 @@ def decorator(func: Callable) -> Callable:
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 @@ def include_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 @@ def get(
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 @@ def get(
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 @@ def put(
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 @@ def put(
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 @@ def post(
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 @@ def post(
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 @@ def delete(
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 @@ def delete(
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 @@ def options(
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 @@ def options(
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 @@ def head(
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 @@ def head(
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 @@ def patch(
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 @@ def patch(
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 @@ def trace(
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 @@ def trace(
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,
diff --git a/fastapi/utils.py b/fastapi/utils.py
index 9e4210f86ef84..b817a10d0fe70 100644
--- a/fastapi/utils.py
+++ b/fastapi/utils.py
@@ -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
diff --git a/pyproject.toml b/pyproject.toml
index 21cade15cc51c..2c62c572b5229 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -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"
diff --git a/tests/test_application.py b/tests/test_application.py
index 674c38a5f0c74..bdbff1cf6ff0d 100644
--- a/tests/test_application.py
+++ b/tests/test_application.py
@@ -68,7 +68,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id"},
+ "schema": {"title": "Item Id"},
"name": "item_id",
"in": "path",
}
@@ -98,7 +98,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -128,7 +128,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "integer"},
+ "schema": {"title": "Item Id", "type": "integer"},
"name": "item_id",
"in": "path",
}
@@ -158,7 +158,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "number"},
+ "schema": {"title": "Item Id", "type": "number"},
"name": "item_id",
"in": "path",
}
@@ -188,7 +188,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "boolean"},
+ "schema": {"title": "Item Id", "type": "boolean"},
"name": "item_id",
"in": "path",
}
@@ -218,7 +218,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -248,7 +248,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -279,7 +279,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"minLength": 3,
"type": "string",
},
@@ -313,7 +313,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"maxLength": 3,
"type": "string",
},
@@ -347,7 +347,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"maxLength": 3,
"minLength": 2,
"type": "string",
@@ -382,7 +382,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"exclusiveMinimum": 3.0,
"type": "number",
},
@@ -416,7 +416,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"exclusiveMinimum": 0.0,
"type": "number",
},
@@ -450,7 +450,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"minimum": 3.0,
"type": "number",
},
@@ -484,7 +484,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"exclusiveMaximum": 3.0,
"type": "number",
},
@@ -518,7 +518,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"exclusiveMaximum": 0.0,
"type": "number",
},
@@ -552,7 +552,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"maximum": 3.0,
"type": "number",
},
@@ -586,7 +586,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"exclusiveMaximum": 3.0,
"exclusiveMinimum": 1.0,
"type": "number",
@@ -621,7 +621,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"maximum": 3.0,
"minimum": 1.0,
"type": "number",
@@ -656,7 +656,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"exclusiveMaximum": 3.0,
"type": "integer",
},
@@ -690,7 +690,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"exclusiveMinimum": 3.0,
"type": "integer",
},
@@ -724,7 +724,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"maximum": 3.0,
"type": "integer",
},
@@ -758,7 +758,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"minimum": 3.0,
"type": "integer",
},
@@ -792,7 +792,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"exclusiveMaximum": 3.0,
"exclusiveMinimum": 1.0,
"type": "integer",
@@ -827,7 +827,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"maximum": 3.0,
"minimum": 1.0,
"type": "integer",
diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py
index 812a48fea3f40..f5bd7b3f20e71 100644
--- a/tests/test_extra_routes.py
+++ b/tests/test_extra_routes.py
@@ -77,7 +77,7 @@ def trace_item(item_id: str):
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -105,7 +105,7 @@ def trace_item(item_id: str):
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -141,7 +141,7 @@ def trace_item(item_id: str):
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -169,7 +169,7 @@ def trace_item(item_id: str):
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -197,7 +197,7 @@ def trace_item(item_id: str):
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -233,7 +233,7 @@ def trace_item(item_id: str):
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -263,7 +263,7 @@ def trace_item(item_id: str):
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_infer_param_optionality.py b/tests/test_infer_param_optionality.py
index 79fa716fe7aa9..3175f72d30b80 100644
--- a/tests/test_infer_param_optionality.py
+++ b/tests/test_infer_param_optionality.py
@@ -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",
}
diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py
index f079059edc356..0975430d78e31 100644
--- a/tests/test_jsonable_encoder.py
+++ b/tests/test_jsonable_encoder.py
@@ -3,7 +3,13 @@
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 Config:
class ModelWithAlias(BaseModel):
- foo: str = Schema(..., alias="Foo")
+ foo: str = Field(..., alias="Foo")
def test_encode_class():
diff --git a/tests/test_path.py b/tests/test_path.py
index cde7a9d02b6df..f1ef58038b0e4 100644
--- a/tests/test_path.py
+++ b/tests/test_path.py
@@ -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 @@ def test_nonexistent():
("/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),
diff --git a/tests/test_put_no_body.py b/tests/test_put_no_body.py
index f70d6773e0682..1f1a1c00a7102 100644
--- a/tests/test_put_no_body.py
+++ b/tests/test_put_no_body.py
@@ -39,7 +39,7 @@ def save_item_no_body(item_id: str):
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_security_oauth2.py b/tests/test_security_oauth2.py
index 5cf2592f314dc..233123337078b 100644
--- a/tests/test_security_oauth2.py
+++ b/tests/test_security_oauth2.py
@@ -99,15 +99,15 @@ def read_current_user(current_user: "User" = Depends(get_current_user)):
"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": {
diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py
index f85db00d34519..cc2c5f0fd20e9 100644
--- a/tests/test_security_oauth2_optional.py
+++ b/tests/test_security_oauth2_optional.py
@@ -103,15 +103,15 @@ def read_current_user(current_user: Optional[User] = Depends(get_current_user)):
"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": {
diff --git a/tests/test_skip_defaults.py b/tests/test_skip_defaults.py
index 9997ba7b2c4b1..36b2de53768db 100644
--- a/tests/test_skip_defaults.py
+++ b/tests/test_skip_defaults.py
@@ -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)
diff --git a/tests/test_starlette_exception.py b/tests/test_starlette_exception.py
index 74db57142f38c..706957b9913d3 100644
--- a/tests/test_starlette_exception.py
+++ b/tests/test_starlette_exception.py
@@ -54,7 +54,7 @@ async def create_item(item_id: str):
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -84,7 +84,7 @@ async def create_item(item_id: str):
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial001.py b/tests/test_tutorial/test_additional_responses/test_tutorial001.py
index 5036afda2e844..839bb1d53e00f 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial001.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial001.py
@@ -43,7 +43,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py
index ac63ff2edf222..722ef83db6f86 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py
@@ -39,7 +39,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
},
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial003.py b/tests/test_tutorial/test_additional_responses/test_tutorial003.py
index aa90fff1709a6..4f270efdc2403 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial003.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial003.py
@@ -44,7 +44,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py
index d26183961fc8a..4608799bf775e 100644
--- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py
+++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py
@@ -42,7 +42,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
},
diff --git a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py
index be5f56dbd5daa..91a8ba96797d4 100644
--- a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py
+++ b/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py
@@ -14,7 +14,7 @@
"content": {
"application/json": {
"schema": {
- "title": "Response_Read_Notes_Notes__Get",
+ "title": "Response Read Notes Notes Get",
"type": "array",
"items": {"$ref": "#/components/schemas/Note"},
}
diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py
index d0eff5b651504..794e48e1d5f2a 100644
--- a/tests/test_tutorial/test_bigger_applications/test_main.py
+++ b/tests/test_tutorial/test_bigger_applications/test_main.py
@@ -123,7 +123,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
},
@@ -160,7 +160,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
},
diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
index dc7e518e249a1..9141e7f48d72e 100644
--- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
+++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py
@@ -32,7 +32,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "integer"},
+ "schema": {"title": "Item Id", "type": "integer"},
"name": "item_id",
"in": "path",
}
@@ -69,7 +69,7 @@
"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": {
diff --git a/tests/test_tutorial/test_body_schema/test_tutorial001.py b/tests/test_tutorial/test_body_schema/test_tutorial001.py
index eb938839684f4..2d822cbfe8ac3 100644
--- a/tests/test_tutorial/test_body_schema/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_schema/test_tutorial001.py
@@ -3,6 +3,15 @@
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 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "integer"},
+ "schema": {"title": "Item Id", "type": "integer"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py
index 76a368c663dba..bfd62e927c3be 100644
--- a/tests/test_tutorial/test_body_updates/test_tutorial001.py
+++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py
@@ -35,7 +35,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -67,7 +67,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py
index 1cf1b492b6f75..d6aed3ca77148 100644
--- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py
@@ -32,7 +32,7 @@
"parameters": [
{
"required": False,
- "schema": {"title": "Ads_Id", "type": "string"},
+ "schema": {"title": "Ads Id", "type": "string"},
"name": "ads_id",
"in": "cookie",
}
diff --git a/tests/test_tutorial/test_events/test_tutorial001.py b/tests/test_tutorial/test_events/test_tutorial001.py
index 32f0b56ce087e..bf7cd532b8bbc 100644
--- a/tests/test_tutorial/test_events/test_tutorial001.py
+++ b/tests/test_tutorial/test_events/test_tutorial001.py
@@ -29,7 +29,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py
index 6c53b7adcf3df..846d3c19119b2 100644
--- a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py
+++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py
@@ -33,7 +33,7 @@
{
"required": True,
"schema": {
- "title": "Item_Id",
+ "title": "Item Id",
"type": "string",
"format": "uuid",
},
@@ -60,22 +60,22 @@
"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",
},
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003.py b/tests/test_tutorial/test_extra_models/test_tutorial003.py
index 95aef55cb7fbe..7b4776d155e2a 100644
--- a/tests/test_tutorial/test_extra_models/test_tutorial003.py
+++ b/tests/test_tutorial/test_extra_models/test_tutorial003.py
@@ -16,7 +16,7 @@
"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 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004.py b/tests/test_tutorial/test_extra_models/test_tutorial004.py
index 17ea0e1be3ac8..1ee2980c0ebab 100644
--- a/tests/test_tutorial/test_extra_models/test_tutorial004.py
+++ b/tests/test_tutorial/test_extra_models/test_tutorial004.py
@@ -16,7 +16,7 @@
"content": {
"application/json": {
"schema": {
- "title": "Response_Read_Items_Items__Get",
+ "title": "Response Read Items Items Get",
"type": "array",
"items": {"$ref": "#/components/schemas/Item"},
}
diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005.py b/tests/test_tutorial/test_extra_models/test_tutorial005.py
index d8259f1cdcdd0..7f815e08a7463 100644
--- a/tests/test_tutorial/test_extra_models/test_tutorial005.py
+++ b/tests/test_tutorial/test_extra_models/test_tutorial005.py
@@ -16,7 +16,7 @@
"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"},
}
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial001.py b/tests/test_tutorial/test_handling_errors/test_tutorial001.py
index 93cd6c12e8117..3869cb485f85d 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial001.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial001.py
@@ -31,7 +31,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial002.py b/tests/test_tutorial/test_handling_errors/test_tutorial002.py
index 7d9039e885289..3381f9d72042e 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial002.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial002.py
@@ -31,7 +31,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial004.py b/tests/test_tutorial/test_handling_errors/test_tutorial004.py
index 38a9c20b24527..83eb3675ceb17 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial004.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial004.py
@@ -31,7 +31,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "integer"},
+ "schema": {"title": "Item Id", "type": "integer"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial005.py b/tests/test_tutorial/test_handling_errors/test_tutorial005.py
index a59399a785b11..4813201c83bfb 100644
--- a/tests/test_tutorial/test_handling_errors/test_tutorial005.py
+++ b/tests/test_tutorial/test_handling_errors/test_tutorial005.py
@@ -31,7 +31,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "integer"},
+ "schema": {"title": "Item Id", "type": "integer"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_path_params/test_tutorial004.py b/tests/test_tutorial/test_path_params/test_tutorial004.py
index e4590db7b894a..a131b81cab9ca 100644
--- a/tests/test_tutorial/test_path_params/test_tutorial004.py
+++ b/tests/test_tutorial/test_path_params/test_tutorial004.py
@@ -31,7 +31,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "File_Path", "type": "string"},
+ "schema": {"title": "File Path", "type": "string"},
"name": "file_path",
"in": "path",
}
diff --git a/tests/test_tutorial/test_path_params/test_tutorial005.py b/tests/test_tutorial/test_path_params/test_tutorial005.py
index 4ff7e5e29af9e..91cc6db64e0b1 100644
--- a/tests/test_tutorial/test_path_params/test_tutorial005.py
+++ b/tests/test_tutorial/test_path_params/test_tutorial005.py
@@ -33,7 +33,7 @@
{
"required": True,
"schema": {
- "title": "Model_Name",
+ "title": "Model Name",
"enum": ["alexnet", "resnet", "lenet"],
"type": "string",
},
diff --git a/tests/test_tutorial/test_query_params/test_tutorial005.py b/tests/test_tutorial/test_query_params/test_tutorial005.py
index 16d04afa29f9c..e6db1b0f0d779 100644
--- a/tests/test_tutorial/test_query_params/test_tutorial005.py
+++ b/tests/test_tutorial/test_query_params/test_tutorial005.py
@@ -32,7 +32,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
},
diff --git a/tests/test_tutorial/test_query_params/test_tutorial006.py b/tests/test_tutorial/test_query_params/test_tutorial006.py
index bbe5afc55073c..edcf50697e03a 100644
--- a/tests/test_tutorial/test_query_params/test_tutorial006.py
+++ b/tests/test_tutorial/test_query_params/test_tutorial006.py
@@ -32,7 +32,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
},
diff --git a/tests/test_tutorial/test_query_params/test_tutorial007.py b/tests/test_tutorial/test_query_params/test_tutorial007.py
index a0fb238501850..f66791a509b6d 100644
--- a/tests/test_tutorial/test_query_params/test_tutorial007.py
+++ b/tests/test_tutorial/test_query_params/test_tutorial007.py
@@ -31,7 +31,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
},
diff --git a/tests/test_tutorial/test_response_model/test_tutorial003.py b/tests/test_tutorial/test_response_model/test_tutorial003.py
index 569e96e141155..9cf3158252e86 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial003.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial003.py
@@ -52,7 +52,7 @@
"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 @@
"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": {
diff --git a/tests/test_tutorial/test_response_model/test_tutorial004.py b/tests/test_tutorial/test_response_model/test_tutorial004.py
index c609d690d5ebb..3b71420b60002 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial004.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial004.py
@@ -36,7 +36,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_response_model/test_tutorial005.py b/tests/test_tutorial/test_response_model/test_tutorial005.py
index b087395ff4ae6..d0efe22b94725 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial005.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial005.py
@@ -35,7 +35,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -69,7 +69,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_response_model/test_tutorial006.py b/tests/test_tutorial/test_response_model/test_tutorial006.py
index d7d2bce82901e..232ce0f794b14 100644
--- a/tests/test_tutorial/test_response_model/test_tutorial006.py
+++ b/tests/test_tutorial/test_response_model/test_tutorial006.py
@@ -35,7 +35,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
@@ -69,7 +69,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "Item_Id", "type": "string"},
+ "schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
}
diff --git a/tests/test_tutorial/test_security/test_tutorial003.py b/tests/test_tutorial/test_security/test_tutorial003.py
index ebd8731aba575..82b2d959dca12 100644
--- a/tests/test_tutorial/test_security/test_tutorial003.py
+++ b/tests/test_tutorial/test_security/test_tutorial003.py
@@ -62,15 +62,15 @@
"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": {
diff --git a/tests/test_tutorial/test_security/test_tutorial005.py b/tests/test_tutorial/test_security/test_tutorial005.py
index 786fbc3ac577d..a3f8a9ffd5ed8 100644
--- a/tests/test_tutorial/test_security/test_tutorial005.py
+++ b/tests/test_tutorial/test_security/test_tutorial005.py
@@ -103,7 +103,7 @@
"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 @@
"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 @@
"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": {
diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases.py b/tests/test_tutorial/test_sql_databases/test_sql_databases.py
index a85a7f91427d3..e4adc1f7319a3 100644
--- a/tests/test_tutorial/test_sql_databases/test_sql_databases.py
+++ b/tests/test_tutorial/test_sql_databases/test_sql_databases.py
@@ -15,7 +15,7 @@
"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 @@
"parameters": [
{
"required": True,
- "schema": {"title": "User_Id", "type": "integer"},
+ "schema": {"title": "User Id", "type": "integer"},
"name": "user_id",
"in": "path",
}
@@ -144,7 +144,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "User_Id", "type": "integer"},
+ "schema": {"title": "User Id", "type": "integer"},
"name": "user_id",
"in": "path",
}
@@ -167,7 +167,7 @@
"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 @@
"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 @@
"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",
diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py
index d5644d87651d1..c056e18000250 100644
--- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py
+++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py
@@ -15,7 +15,7 @@
"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 @@
"parameters": [
{
"required": True,
- "schema": {"title": "User_Id", "type": "integer"},
+ "schema": {"title": "User Id", "type": "integer"},
"name": "user_id",
"in": "path",
}
@@ -144,7 +144,7 @@
"parameters": [
{
"required": True,
- "schema": {"title": "User_Id", "type": "integer"},
+ "schema": {"title": "User Id", "type": "integer"},
"name": "user_id",
"in": "path",
}
@@ -167,7 +167,7 @@
"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 @@
"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 @@
"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",