Skip to content

Commit e1758d1

Browse files
authored
⬆ Require Pydantic > 1.0 (#1862)
* 🔥 Remove support for Pydantic < 1.0 * 🔥 Remove deprecated skip_defaults from jsonable_encoder and set default for exclude to None, as in Pydantic * ♻️ Set default of response_model_exclude=None as in Pydantic * ⬆️ Require Pydantic >=1.0.0 in requirements
1 parent 3390182 commit e1758d1

File tree

14 files changed

+115
-396
lines changed

14 files changed

+115
-396
lines changed

fastapi/applications.py

Lines changed: 20 additions & 72 deletions
Large diffs are not rendered by default.

fastapi/dependencies/models.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
from typing import Callable, List, Optional, Sequence
22

33
from fastapi.security.base import SecurityBase
4-
5-
try:
6-
from pydantic.fields import ModelField
7-
except ImportError: # pragma: nocover
8-
# TODO: remove when removing support for Pydantic < 1.0.0
9-
from pydantic.fields import Field as ModelField # type: ignore
4+
from pydantic.fields import ModelField
105

116
param_supported_types = (str, int, float, bool)
127

fastapi/dependencies/utils.py

Lines changed: 34 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,23 @@
2828
from fastapi.security.base import SecurityBase
2929
from fastapi.security.oauth2 import OAuth2, SecurityScopes
3030
from fastapi.security.open_id_connect_url import OpenIdConnect
31-
from fastapi.utils import (
32-
PYDANTIC_1,
33-
create_response_field,
34-
get_field_info,
35-
get_path_param_names,
36-
)
37-
from pydantic import BaseConfig, BaseModel, create_model
31+
from fastapi.utils import create_response_field, get_path_param_names
32+
from pydantic import BaseModel, create_model
3833
from pydantic.error_wrappers import ErrorWrapper
3934
from pydantic.errors import MissingError
35+
from pydantic.fields import (
36+
SHAPE_LIST,
37+
SHAPE_SEQUENCE,
38+
SHAPE_SET,
39+
SHAPE_SINGLETON,
40+
SHAPE_TUPLE,
41+
SHAPE_TUPLE_ELLIPSIS,
42+
FieldInfo,
43+
ModelField,
44+
Required,
45+
)
46+
from pydantic.schema import get_annotation_from_field_info
47+
from pydantic.typing import ForwardRef, evaluate_forwardref
4048
from pydantic.utils import lenient_issubclass
4149
from starlette.background import BackgroundTasks
4250
from starlette.concurrency import run_in_threadpool
@@ -45,41 +53,6 @@
4553
from starlette.responses import Response
4654
from starlette.websockets import WebSocket
4755

48-
try:
49-
from pydantic.fields import (
50-
SHAPE_LIST,
51-
SHAPE_SEQUENCE,
52-
SHAPE_SET,
53-
SHAPE_SINGLETON,
54-
SHAPE_TUPLE,
55-
SHAPE_TUPLE_ELLIPSIS,
56-
FieldInfo,
57-
ModelField,
58-
Required,
59-
)
60-
from pydantic.schema import get_annotation_from_field_info
61-
from pydantic.typing import ForwardRef, evaluate_forwardref
62-
except ImportError: # pragma: nocover
63-
# TODO: remove when removing support for Pydantic < 1.0.0
64-
from pydantic import Schema as FieldInfo # type: ignore
65-
from pydantic.fields import Field as ModelField # type: ignore
66-
from pydantic.fields import Required, Shape # type: ignore
67-
from pydantic.schema import get_annotation_from_schema # type: ignore
68-
from pydantic.utils import ForwardRef, evaluate_forwardref # type: ignore
69-
70-
SHAPE_LIST = Shape.LIST
71-
SHAPE_SEQUENCE = Shape.SEQUENCE
72-
SHAPE_SET = Shape.SET
73-
SHAPE_SINGLETON = Shape.SINGLETON
74-
SHAPE_TUPLE = Shape.TUPLE
75-
SHAPE_TUPLE_ELLIPSIS = Shape.TUPLE_ELLIPS
76-
77-
def get_annotation_from_field_info(
78-
annotation: Any, field_info: FieldInfo, field_name: str
79-
) -> Type[Any]:
80-
return get_annotation_from_schema(annotation, field_info)
81-
82-
8356
sequence_shapes = {
8457
SHAPE_LIST,
8558
SHAPE_SET,
@@ -113,7 +86,7 @@ def get_annotation_from_field_info(
11386

11487

11588
def check_file_field(field: ModelField) -> None:
116-
field_info = get_field_info(field)
89+
field_info = field.field_info
11790
if isinstance(field_info, params.Form):
11891
try:
11992
# __version__ is available in both multiparts, and can be mocked
@@ -239,7 +212,7 @@ def get_flat_params(dependant: Dependant) -> List[ModelField]:
239212

240213

241214
def is_scalar_field(field: ModelField) -> bool:
242-
field_info = get_field_info(field)
215+
field_info = field.field_info
243216
if not (
244217
field.shape == SHAPE_SINGLETON
245218
and not lenient_issubclass(field.type_, BaseModel)
@@ -354,7 +327,7 @@ def get_dependant(
354327
) and is_scalar_sequence_field(param_field):
355328
add_param_to_fields(field=param_field, dependant=dependant)
356329
else:
357-
field_info = get_field_info(param_field)
330+
field_info = param_field.field_info
358331
assert isinstance(
359332
field_info, params.Body
360333
), f"Param: {param_field.name} can only be a request body, using Body(...)"
@@ -430,16 +403,13 @@ def get_param_field(
430403
)
431404
field.required = required
432405
if not had_schema and not is_scalar_field(field=field):
433-
if PYDANTIC_1:
434-
field.field_info = params.Body(field_info.default)
435-
else:
436-
field.schema = params.Body(field_info.default) # type: ignore # pragma: nocover
406+
field.field_info = params.Body(field_info.default)
437407

438408
return field
439409

440410

441411
def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None:
442-
field_info = cast(params.Param, get_field_info(field))
412+
field_info = cast(params.Param, field.field_info)
443413
if field_info.in_ == params.ParamTypes.path:
444414
dependant.path_params.append(field)
445415
elif field_info.in_ == params.ParamTypes.query:
@@ -642,26 +612,17 @@ def request_params_to_args(
642612
value = received_params.getlist(field.alias) or field.default
643613
else:
644614
value = received_params.get(field.alias)
645-
field_info = get_field_info(field)
615+
field_info = field.field_info
646616
assert isinstance(
647617
field_info, params.Param
648618
), "Params must be subclasses of Param"
649619
if value is None:
650620
if field.required:
651-
if PYDANTIC_1:
652-
errors.append(
653-
ErrorWrapper(
654-
MissingError(), loc=(field_info.in_.value, field.alias)
655-
)
656-
)
657-
else: # pragma: nocover
658-
errors.append(
659-
ErrorWrapper( # type: ignore
660-
MissingError(),
661-
loc=(field_info.in_.value, field.alias),
662-
config=BaseConfig,
663-
)
621+
errors.append(
622+
ErrorWrapper(
623+
MissingError(), loc=(field_info.in_.value, field.alias)
664624
)
625+
)
665626
else:
666627
values[field.name] = deepcopy(field.default)
667628
continue
@@ -685,7 +646,7 @@ async def request_body_to_args(
685646
errors = []
686647
if required_params:
687648
field = required_params[0]
688-
field_info = get_field_info(field)
649+
field_info = field.field_info
689650
embed = getattr(field_info, "embed", None)
690651
field_alias_omitted = len(required_params) == 1 and not embed
691652
if field_alias_omitted:
@@ -752,12 +713,7 @@ async def request_body_to_args(
752713

753714

754715
def get_missing_field_error(loc: Tuple[str, ...]) -> ErrorWrapper:
755-
if PYDANTIC_1:
756-
missing_field_error = ErrorWrapper(MissingError(), loc=loc)
757-
else: # pragma: no cover
758-
missing_field_error = ErrorWrapper( # type: ignore
759-
MissingError(), loc=loc, config=BaseConfig,
760-
)
716+
missing_field_error = ErrorWrapper(MissingError(), loc=loc)
761717
return missing_field_error
762718

763719

@@ -775,7 +731,7 @@ def get_schema_compatible_field(*, field: ModelField) -> ModelField:
775731
default=field.default,
776732
required=field.required,
777733
alias=field.alias,
778-
field_info=get_field_info(field),
734+
field_info=field.field_info,
779735
)
780736
return out_field
781737

@@ -785,7 +741,7 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
785741
if not flat_dependant.body_params:
786742
return None
787743
first_param = flat_dependant.body_params[0]
788-
field_info = get_field_info(first_param)
744+
field_info = first_param.field_info
789745
embed = getattr(field_info, "embed", None)
790746
body_param_names_set = {param.name for param in flat_dependant.body_params}
791747
if len(body_param_names_set) == 1 and not embed:
@@ -796,29 +752,25 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
796752
# in case a sub-dependency is evaluated with a single unique body field
797753
# That is combined (embedded) with other body fields
798754
for param in flat_dependant.body_params:
799-
setattr(get_field_info(param), "embed", True)
755+
setattr(param.field_info, "embed", True)
800756
model_name = "Body_" + name
801757
BodyModel = create_model(model_name)
802758
for f in flat_dependant.body_params:
803759
BodyModel.__fields__[f.name] = get_schema_compatible_field(field=f)
804760
required = any(True for f in flat_dependant.body_params if f.required)
805761

806762
BodyFieldInfo_kwargs: Dict[str, Any] = dict(default=None)
807-
if any(
808-
isinstance(get_field_info(f), params.File) for f in flat_dependant.body_params
809-
):
763+
if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params):
810764
BodyFieldInfo: Type[params.Body] = params.File
811-
elif any(
812-
isinstance(get_field_info(f), params.Form) for f in flat_dependant.body_params
813-
):
765+
elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params):
814766
BodyFieldInfo = params.Form
815767
else:
816768
BodyFieldInfo = params.Body
817769

818770
body_param_media_types = [
819-
getattr(get_field_info(f), "media_type")
771+
getattr(f.field_info, "media_type")
820772
for f in flat_dependant.body_params
821-
if isinstance(get_field_info(f), params.Body)
773+
if isinstance(f.field_info, params.Body)
822774
]
823775
if len(set(body_param_media_types)) == 1:
824776
BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0]

fastapi/encoders.py

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
from types import GeneratorType
55
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
66

7-
from fastapi.logger import logger
8-
from fastapi.utils import PYDANTIC_1
97
from pydantic import BaseModel
108
from pydantic.json import ENCODERS_BY_TYPE
119

@@ -28,21 +26,14 @@ def generate_encoders_by_class_tuples(
2826
def jsonable_encoder(
2927
obj: Any,
3028
include: Optional[Union[SetIntStr, DictIntStrAny]] = None,
31-
exclude: Union[SetIntStr, DictIntStrAny] = set(),
29+
exclude: Optional[Union[SetIntStr, DictIntStrAny]] = None,
3230
by_alias: bool = True,
33-
skip_defaults: Optional[bool] = None,
3431
exclude_unset: bool = False,
3532
exclude_defaults: bool = False,
3633
exclude_none: bool = False,
3734
custom_encoder: dict = {},
3835
sqlalchemy_safe: bool = True,
3936
) -> Any:
40-
if skip_defaults is not None:
41-
logger.warning( # pragma: nocover
42-
"skip_defaults in jsonable_encoder has been deprecated in favor of "
43-
"exclude_unset to keep in line with Pydantic v1, support for it will be "
44-
"removed soon."
45-
)
4637
if include is not None and not isinstance(include, set):
4738
include = set(include)
4839
if exclude is not None and not isinstance(exclude, set):
@@ -51,24 +42,14 @@ def jsonable_encoder(
5142
encoder = getattr(obj.__config__, "json_encoders", {})
5243
if custom_encoder:
5344
encoder.update(custom_encoder)
54-
if PYDANTIC_1:
55-
obj_dict = obj.dict(
56-
include=include,
57-
exclude=exclude,
58-
by_alias=by_alias,
59-
exclude_unset=bool(exclude_unset or skip_defaults),
60-
exclude_none=exclude_none,
61-
exclude_defaults=exclude_defaults,
62-
)
63-
else: # pragma: nocover
64-
if exclude_defaults:
65-
raise ValueError("Cannot use exclude_defaults")
66-
obj_dict = obj.dict(
67-
include=include,
68-
exclude=exclude,
69-
by_alias=by_alias,
70-
skip_defaults=bool(exclude_unset or skip_defaults),
71-
)
45+
obj_dict = obj.dict(
46+
include=include,
47+
exclude=exclude,
48+
by_alias=by_alias,
49+
exclude_unset=exclude_unset,
50+
exclude_none=exclude_none,
51+
exclude_defaults=exclude_defaults,
52+
)
7253
if "__root__" in obj_dict:
7354
obj_dict = obj_dict["__root__"]
7455
return jsonable_encoder(
@@ -94,7 +75,7 @@ def jsonable_encoder(
9475
or (not key.startswith("_sa"))
9576
)
9677
and (value is not None or not exclude_none)
97-
and ((include and key in include) or key not in exclude)
78+
and ((include and key in include) or not exclude or key not in exclude)
9879
):
9980
encoded_key = jsonable_encoder(
10081
key,

fastapi/exceptions.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
from typing import Any, Dict, Optional, Sequence
22

3-
from fastapi.utils import PYDANTIC_1
43
from pydantic import ValidationError, create_model
54
from pydantic.error_wrappers import ErrorList
65
from starlette.exceptions import HTTPException as StarletteHTTPException
7-
from starlette.requests import Request
8-
from starlette.websockets import WebSocket
96

107

118
class HTTPException(StarletteHTTPException):
@@ -32,15 +29,9 @@ class FastAPIError(RuntimeError):
3229
class RequestValidationError(ValidationError):
3330
def __init__(self, errors: Sequence[ErrorList], *, body: Any = None) -> None:
3431
self.body = body
35-
if PYDANTIC_1:
36-
super().__init__(errors, RequestErrorModel)
37-
else:
38-
super().__init__(errors, Request) # type: ignore # pragma: nocover
32+
super().__init__(errors, RequestErrorModel)
3933

4034

4135
class WebSocketRequestValidationError(ValidationError):
4236
def __init__(self, errors: Sequence[ErrorList]) -> None:
43-
if PYDANTIC_1:
44-
super().__init__(errors, WebSocketErrorModel)
45-
else:
46-
super().__init__(errors, WebSocket) # type: ignore # pragma: nocover
37+
super().__init__(errors, WebSocketErrorModel)

fastapi/openapi/models.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,13 @@
22
from typing import Any, Callable, Dict, Iterable, List, Optional, Union
33

44
from fastapi.logger import logger
5-
from pydantic import BaseModel
6-
7-
try:
8-
from pydantic import AnyUrl, Field
9-
except ImportError: # pragma: nocover
10-
# TODO: remove when removing support for Pydantic < 1.0.0
11-
from pydantic import Schema as Field # type: ignore
12-
from pydantic import UrlStr as AnyUrl # type: ignore
5+
from pydantic import AnyUrl, BaseModel, Field
136

147
try:
158
import email_validator
169

1710
assert email_validator # make autoflake ignore the unused import
18-
try:
19-
from pydantic import EmailStr
20-
except ImportError: # pragma: nocover
21-
# TODO: remove when removing support for Pydantic < 1.0.0
22-
from pydantic.types import EmailStr # type: ignore
11+
from pydantic import EmailStr
2312
except ImportError: # pragma: no cover
2413

2514
class EmailStr(str): # type: ignore

0 commit comments

Comments
 (0)