diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index 40810eab7a5f8..4581c38c88ede 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -26,7 +26,7 @@ from .v2 import Url as Url from .v2 import copy_field_info as copy_field_info 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_definitions as get_definitions from .v2 import get_flat_models_from_fields as get_flat_models_from_fields diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 535af0784991d..3b64fba76c900 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -22,10 +22,10 @@ from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation 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] GetJsonSchemaHandler as GetJsonSchemaHandler, ) -from pydantic._internal._typing_extra import eval_type_lenient # ty: ignore[deprecated] from pydantic.fields import FieldInfo as FieldInfo from pydantic.json_schema import GenerateJsonSchema as _GenerateJsonSchema from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue @@ -38,7 +38,20 @@ RequiredParam = 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): diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 6b14dac8dc55e..aceca6a1d3a65 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -33,7 +33,7 @@ Undefined, copy_field_info, create_body_model, - evaluate_forwardref, # ty: ignore[deprecated] + evaluate_forwardref, field_annotation_is_scalar, field_annotation_is_scalar_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: if isinstance(annotation, str): annotation = ForwardRef(annotation) - annotation = evaluate_forwardref(annotation, globalns, globalns) # ty: ignore[deprecated] + annotation = evaluate_forwardref(annotation, globalns, globalns) if annotation is type(None): return None return annotation diff --git a/fastapi/encoders.py b/fastapi/encoders.py index 84893dc808ad8..43f24101b6f8b 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -22,7 +22,6 @@ from fastapi.exceptions import PydanticV1NotSupportedError from fastapi.types import IncEx from pydantic import BaseModel -from pydantic.color import Color # ty: ignore[deprecated] from pydantic.networks import AnyUrl, NameEmail from pydantic.types import SecretBytes, SecretStr from pydantic_core import PydanticUndefinedType @@ -32,6 +31,23 @@ 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 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]] = { bytes: lambda o: o.decode(), - Color: str, # ty: ignore[deprecated] + Color: str, + PyExtraColor: str, datetime.date: isoformat, datetime.datetime: isoformat, datetime.time: isoformat, diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index 595202beaf371..c23a9e5d7918a 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -311,3 +311,21 @@ class Model(BaseModel): def test_encode_pydantic_undefined(): data = {"value": Undefined} 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"}