Browse Source

♻️ Handle Pydantic v1 imports for checks conditionally, filtering warnings

pull/14609/head
Sebastián Ramírez 7 months ago
parent
commit
b32e65f5af
  1. 2
      fastapi/_compat/__init__.py
  2. 3
      fastapi/_compat/may_v1.py
  3. 20
      fastapi/_compat/shared.py
  4. 9
      fastapi/encoders.py
  5. 8
      tests/test_jsonable_encoder.py
  6. 6
      tests/test_pydantic_v1_error.py

2
fastapi/_compat/__init__.py

@ -31,6 +31,8 @@ from .shared import PYDANTIC_V2 as PYDANTIC_V2
from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE
from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1
from .shared import field_annotation_is_scalar as field_annotation_is_scalar
from .shared import is_pydantic_v1_model_class as is_pydantic_v1_model_class
from .shared import is_pydantic_v1_model_instance as is_pydantic_v1_model_instance
from .shared import (
is_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation,
)

3
fastapi/_compat/may_v1.py

@ -1,9 +1,6 @@
from collections.abc import Sequence
from typing import Any, Union
from pydantic.v1 import BaseModel as BaseModel
from pydantic.v1.fields import ModelField as ModelField
def _regenerate_error_with_loc(
*, errors: Sequence[Any], loc_prefix: tuple[Union[str, int], ...]

20
fastapi/_compat/shared.py

@ -1,6 +1,7 @@
import sys
import types
import typing
import warnings
from collections import deque
from collections.abc import Mapping, Sequence
from dataclasses import is_dataclass
@ -10,7 +11,6 @@ from typing import (
Union,
)
from fastapi._compat import may_v1
from fastapi.types import UnionType
from pydantic import BaseModel
from pydantic.version import VERSION as PYDANTIC_VERSION
@ -177,13 +177,27 @@ def is_uploadfile_sequence_annotation(annotation: Any) -> bool:
)
def is_pydantic_v1_model_instance(obj: Any) -> bool:
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
from pydantic import v1
return isinstance(obj, v1.BaseModel)
def is_pydantic_v1_model_class(cls: Any) -> bool:
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
from pydantic import v1
return lenient_issubclass(cls, v1.BaseModel)
def annotation_is_pydantic_v1(annotation: Any) -> bool:
if lenient_issubclass(annotation, may_v1.BaseModel):
if is_pydantic_v1_model_class(annotation):
return True
origin = get_origin(annotation)
if origin is Union or origin is UnionType:
for arg in get_args(annotation):
if lenient_issubclass(arg, may_v1.BaseModel):
if is_pydantic_v1_model_class(arg):
return True
if field_annotation_is_sequence(annotation):
for sub_annotation in get_args(annotation):

9
fastapi/encoders.py

@ -25,7 +25,12 @@ from pydantic.color import Color
from pydantic.networks import AnyUrl, NameEmail
from pydantic.types import SecretBytes, SecretStr
from ._compat import Url, _is_undefined, _model_dump, may_v1
from ._compat import (
Url,
_is_undefined,
_model_dump,
is_pydantic_v1_model_instance,
)
# Taken from Pydantic v1 as is
@ -218,7 +223,7 @@ def jsonable_encoder(
include = set(include)
if exclude is not None and not isinstance(exclude, (set, dict)):
exclude = set(exclude)
if isinstance(obj, may_v1.BaseModel):
if is_pydantic_v1_model_instance(obj):
raise PydanticV1NotSupportedError(
"pydantic.v1 models are no longer supported by FastAPI."
f" Please update the model {obj!r}."

8
tests/test_jsonable_encoder.py

@ -1,3 +1,4 @@
import warnings
from collections import deque
from dataclasses import dataclass
from datetime import datetime, timezone
@ -13,8 +14,6 @@ from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import PydanticV1NotSupportedError
from pydantic import BaseModel, Field, ValidationError
from .utils import needs_pydanticv1
class Person:
def __init__(self, name: str):
@ -157,9 +156,10 @@ def test_encode_custom_json_encoders_model_pydanticv2():
assert jsonable_encoder(subclass_model) == {"dt_field": "2019-01-01T08:00:00+00:00"}
@needs_pydanticv1
def test_json_encoder_error_with_pydanticv1():
from pydantic import v1
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
from pydantic import v1
class ModelV1(v1.BaseModel):
name: str

6
tests/test_pydantic_v1_error.py

@ -1,4 +1,5 @@
import sys
import warnings
import pytest
@ -8,9 +9,12 @@ if sys.version_info >= (3, 14):
skip_module_if_py_gte_314()
from fastapi import FastAPI
from fastapi._compat.may_v1 import BaseModel
from fastapi.exceptions import PydanticV1NotSupportedError
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
from pydantic.v1 import BaseModel
def test_raises_pydantic_v1_model_in_endpoint_param() -> None:
class ParamModelV1(BaseModel):

Loading…
Cancel
Save