Browse Source

⬆️ Update Pydantic v2 code to address deprecations (#15101)

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
pull/15364/head
Sofie Van Landeghem 1 month ago
committed by GitHub
parent
commit
7815a32f2e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      fastapi/_compat/__init__.py
  2. 17
      fastapi/_compat/v2.py
  3. 4
      fastapi/dependencies/utils.py
  4. 21
      fastapi/encoders.py
  5. 18
      tests/test_jsonable_encoder.py

2
fastapi/_compat/__init__.py

@ -26,7 +26,7 @@ from .v2 import Undefined as Undefined
from .v2 import Url as Url from .v2 import Url as Url
from .v2 import copy_field_info as copy_field_info from .v2 import copy_field_info as copy_field_info
from .v2 import create_body_model as create_body_model from .v2 import create_body_model as create_body_model
from .v2 import evaluate_forwardref as evaluate_forwardref # ty: ignore[deprecated] from .v2 import evaluate_forwardref as evaluate_forwardref
from .v2 import get_cached_model_fields as get_cached_model_fields from .v2 import get_cached_model_fields as get_cached_model_fields
from .v2 import get_definitions as get_definitions from .v2 import get_definitions as get_definitions
from .v2 import get_flat_models_from_fields as get_flat_models_from_fields from .v2 import get_flat_models_from_fields as get_flat_models_from_fields

17
fastapi/_compat/v2.py

@ -22,10 +22,10 @@ from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, create_model
from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation
from pydantic import ValidationError as ValidationError from pydantic import ValidationError as ValidationError
from pydantic._internal import _typing_extra as _pydantic_typing_extra
from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-defined] # ty: ignore[unused-ignore-comment] from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-defined] # ty: ignore[unused-ignore-comment]
GetJsonSchemaHandler as GetJsonSchemaHandler, GetJsonSchemaHandler as GetJsonSchemaHandler,
) )
from pydantic._internal._typing_extra import eval_type_lenient # ty: ignore[deprecated]
from pydantic.fields import FieldInfo as FieldInfo from pydantic.fields import FieldInfo as FieldInfo
from pydantic.json_schema import GenerateJsonSchema as _GenerateJsonSchema from pydantic.json_schema import GenerateJsonSchema as _GenerateJsonSchema
from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue
@ -38,7 +38,20 @@ from pydantic_core.core_schema import (
RequiredParam = PydanticUndefined RequiredParam = PydanticUndefined
Undefined = PydanticUndefined Undefined = PydanticUndefined
evaluate_forwardref = eval_type_lenient # ty: ignore[deprecated]
def evaluate_forwardref(
value: Any,
globalns: dict[str, Any] | None = None,
localns: dict[str, Any] | None = None,
) -> Any:
# eval_type_lenient has been deprecated since Pydantic v2.10.0b1 (PR #10530)
try_eval_type = getattr(_pydantic_typing_extra, "try_eval_type", None)
if try_eval_type is not None:
return try_eval_type(value, globalns, localns)[0]
return _pydantic_typing_extra.eval_type_lenient( # ty: ignore[deprecated]
value, globalns, localns
)
class GenerateJsonSchema(_GenerateJsonSchema): class GenerateJsonSchema(_GenerateJsonSchema):

4
fastapi/dependencies/utils.py

@ -33,7 +33,7 @@ from fastapi._compat import (
Undefined, Undefined,
copy_field_info, copy_field_info,
create_body_model, create_body_model,
evaluate_forwardref, # ty: ignore[deprecated] evaluate_forwardref,
field_annotation_is_scalar, field_annotation_is_scalar,
field_annotation_is_scalar_sequence, field_annotation_is_scalar_sequence,
field_annotation_is_sequence, field_annotation_is_sequence,
@ -245,7 +245,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any: def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:
if isinstance(annotation, str): if isinstance(annotation, str):
annotation = ForwardRef(annotation) annotation = ForwardRef(annotation)
annotation = evaluate_forwardref(annotation, globalns, globalns) # ty: ignore[deprecated] annotation = evaluate_forwardref(annotation, globalns, globalns)
if annotation is type(None): if annotation is type(None):
return None return None
return annotation return annotation

21
fastapi/encoders.py

@ -22,7 +22,6 @@ from annotated_doc import Doc
from fastapi.exceptions import PydanticV1NotSupportedError from fastapi.exceptions import PydanticV1NotSupportedError
from fastapi.types import IncEx from fastapi.types import IncEx
from pydantic import BaseModel from pydantic import BaseModel
from pydantic.color import Color # ty: ignore[deprecated]
from pydantic.networks import AnyUrl, NameEmail from pydantic.networks import AnyUrl, NameEmail
from pydantic.types import SecretBytes, SecretStr from pydantic.types import SecretBytes, SecretStr
from pydantic_core import PydanticUndefinedType from pydantic_core import PydanticUndefinedType
@ -32,6 +31,23 @@ from ._compat import (
is_pydantic_v1_model_instance, is_pydantic_v1_model_instance,
) )
try:
# pydantic.color.Color is deprecated since v2.0b3, but supporting for bwd-compat
from pydantic.color import Color # ty: ignore[deprecated]
except ImportError: # pragma: no cover
class Color: # type: ignore[no-redef] # ty: ignore[unused-ignore-comment]
pass
try:
# Supporting the new Color format for newer versions of Pydantic
from pydantic_extra_types.color import Color as PyExtraColor
except ImportError: # pragma: no cover
class PyExtraColor: # type: ignore[no-redef] # ty: ignore[unused-ignore-comment]
pass
# Taken from Pydantic v1 as is # Taken from Pydantic v1 as is
def isoformat(o: datetime.date | datetime.time) -> str: def isoformat(o: datetime.date | datetime.time) -> str:
@ -67,7 +83,8 @@ def decimal_encoder(dec_value: Decimal) -> int | float:
ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = { ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = {
bytes: lambda o: o.decode(), bytes: lambda o: o.decode(),
Color: str, # ty: ignore[deprecated] Color: str,
PyExtraColor: str,
datetime.date: isoformat, datetime.date: isoformat,
datetime.datetime: isoformat, datetime.datetime: isoformat,
datetime.time: isoformat, datetime.time: isoformat,

18
tests/test_jsonable_encoder.py

@ -311,3 +311,21 @@ def test_encode_deque_encodes_child_models():
def test_encode_pydantic_undefined(): def test_encode_pydantic_undefined():
data = {"value": Undefined} data = {"value": Undefined}
assert jsonable_encoder(data) == {"value": None} assert jsonable_encoder(data) == {"value": None}
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
@pytest.mark.parametrize(
"module_path",
[
pytest.param("pydantic.color"),
pytest.param("pydantic_extra_types.color"),
],
)
def test_encode_color(module_path):
try:
Color = __import__(module_path, fromlist=["Color"]).Color
except ImportError: # pragma: no cover
pytest.skip(f"{module_path} not available")
data = {"color": Color("blue")}
assert jsonable_encoder(data) == {"color": "blue"}

Loading…
Cancel
Save