From 5fe7833bb74851ec3e99a03e7ee4462b9468c075 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 12 Mar 2026 22:10:53 +0100 Subject: [PATCH 01/28] use Pydantic's try_eval_type if available (otherwise deprecation warning) --- fastapi/_compat/v2.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 79fba931881e1..76acbe5c5db1f 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] GetJsonSchemaHandler as GetJsonSchemaHandler, ) -from pydantic._internal._typing_extra import eval_type_lenient 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,11 @@ RequiredParam = PydanticUndefined Undefined = PydanticUndefined -evaluate_forwardref = eval_type_lenient + +# eval_type_lenient has been deprecated since Pydantic v2.10.0b1 (PR #10530) +evaluate_forwardref = getattr(_pydantic_typing_extra, "try_eval_type", None) +if evaluate_forwardref is None: + evaluate_forwardref = getattr(_pydantic_typing_extra, "eval_type_lenient") class GenerateJsonSchema(_GenerateJsonSchema): From 94bd71371eb4264fd40279f8b7f32e8f80b55d7e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:14:26 +0000 Subject: [PATCH 02/28] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat/v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 76acbe5c5db1f..38ca8ba7a3655 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -42,7 +42,7 @@ # eval_type_lenient has been deprecated since Pydantic v2.10.0b1 (PR #10530) evaluate_forwardref = getattr(_pydantic_typing_extra, "try_eval_type", None) if evaluate_forwardref is None: - evaluate_forwardref = getattr(_pydantic_typing_extra, "eval_type_lenient") + evaluate_forwardref = _pydantic_typing_extra.eval_type_lenient class GenerateJsonSchema(_GenerateJsonSchema): From a1abbce3a51035e96c9c60be850a24378cf0a8d2 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 12 Mar 2026 22:17:00 +0100 Subject: [PATCH 03/28] assert evaluate_forwardref is not None --- fastapi/_compat/v2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 38ca8ba7a3655..11b17b9fa0dcc 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -43,6 +43,7 @@ evaluate_forwardref = getattr(_pydantic_typing_extra, "try_eval_type", None) if evaluate_forwardref is None: evaluate_forwardref = _pydantic_typing_extra.eval_type_lenient +assert evaluate_forwardref is not None class GenerateJsonSchema(_GenerateJsonSchema): From 97ce5de0bf309ea0c096088247c06545c7400bf6 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 12 Mar 2026 22:35:11 +0100 Subject: [PATCH 04/28] make mypy happy --- fastapi/_compat/v2.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 11b17b9fa0dcc..6cf5c67f7cceb 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -1,6 +1,6 @@ import re import warnings -from collections.abc import Sequence +from collections.abc import Callable, Sequence from copy import copy from dataclasses import dataclass, is_dataclass from enum import Enum @@ -39,11 +39,16 @@ RequiredParam = PydanticUndefined Undefined = PydanticUndefined -# eval_type_lenient has been deprecated since Pydantic v2.10.0b1 (PR #10530) -evaluate_forwardref = getattr(_pydantic_typing_extra, "try_eval_type", None) -if evaluate_forwardref is None: - evaluate_forwardref = _pydantic_typing_extra.eval_type_lenient -assert evaluate_forwardref is not None + +def define_forwardref() -> Callable[..., Any]: + # eval_type_lenient has been deprecated since Pydantic v2.10.0b1 (PR #10530) + eval_type = getattr(_pydantic_typing_extra, "try_eval_type", None) + if eval_type is not None: + return cast(Callable[..., Any], eval_type) + return _pydantic_typing_extra.eval_type_lenient + + +evaluate_forwardref = define_forwardref() class GenerateJsonSchema(_GenerateJsonSchema): From 8e5d327aca80d9c0fa84c9aefcddef77fed37ea9 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 13 Mar 2026 10:18:40 +0100 Subject: [PATCH 05/28] fix return type of eval_type --- fastapi/_compat/v2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 6cf5c67f7cceb..d075c4982230e 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -42,9 +42,9 @@ def define_forwardref() -> Callable[..., Any]: # eval_type_lenient has been deprecated since Pydantic v2.10.0b1 (PR #10530) - eval_type = getattr(_pydantic_typing_extra, "try_eval_type", None) - if eval_type is not None: - return cast(Callable[..., Any], eval_type) + try_eval_type = getattr(_pydantic_typing_extra, "try_eval_type", None) + if try_eval_type is not None: + return lambda *args: try_eval_type(*args)[0] return _pydantic_typing_extra.eval_type_lenient From 2d2fe3ebf389dd68dfbb01a97f01c1b88a036076 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 13 Mar 2026 10:27:29 +0100 Subject: [PATCH 06/28] add no cover for now --- fastapi/_compat/v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index d075c4982230e..c6137e17c89f5 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -45,7 +45,7 @@ def define_forwardref() -> Callable[..., Any]: try_eval_type = getattr(_pydantic_typing_extra, "try_eval_type", None) if try_eval_type is not None: return lambda *args: try_eval_type(*args)[0] - return _pydantic_typing_extra.eval_type_lenient + return _pydantic_typing_extra.eval_type_lenient # pragma: no cover evaluate_forwardref = define_forwardref() From bbf15294ebb2d2cd8a0825ad382844c54668197e Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 13 Mar 2026 10:54:24 +0100 Subject: [PATCH 07/28] coditional import of Color to avoid deprecation warning --- fastapi/_compat/__init__.py | 1 + fastapi/_compat/v2.py | 6 ++++++ fastapi/encoders.py | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index 4581c38c88ede..225e79d9f4485 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -19,6 +19,7 @@ from .shared import lenient_issubclass as lenient_issubclass from .shared import sequence_types as sequence_types from .shared import value_is_sequence as value_is_sequence +from .v2 import Color as Color from .v2 import ModelField as ModelField from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError from .v2 import RequiredParam as RequiredParam diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index c6137e17c89f5..d2ef4a8b85918 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -39,6 +39,12 @@ RequiredParam = PydanticUndefined Undefined = PydanticUndefined +# pydantic.color.Color is deprecated since v2.0b3 +try: + from pydantic_extra_types import Color +except ImportError: + from pydantic.color import Color # noqa: F401 + def define_forwardref() -> Callable[..., Any]: # eval_type_lenient has been deprecated since Pydantic v2.10.0b1 (PR #10530) diff --git a/fastapi/encoders.py b/fastapi/encoders.py index e20255c1100e9..863b5d453129b 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -22,12 +22,12 @@ from fastapi.exceptions import PydanticV1NotSupportedError from fastapi.types import IncEx from pydantic import BaseModel -from pydantic.color import Color from pydantic.networks import AnyUrl, NameEmail from pydantic.types import SecretBytes, SecretStr from pydantic_core import PydanticUndefinedType from ._compat import ( + Color, Url, is_pydantic_v1_model_instance, ) From 171f062dd5b7218de11a065a9784fb9226b9e461 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 13 Mar 2026 11:03:09 +0100 Subject: [PATCH 08/28] fix import --- fastapi/_compat/v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index d2ef4a8b85918..f98013a0b932f 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -41,7 +41,7 @@ # pydantic.color.Color is deprecated since v2.0b3 try: - from pydantic_extra_types import Color + from pydantic_extra_types.color import Color except ImportError: from pydantic.color import Color # noqa: F401 From e86753477a60b29a2d3df935313ca812c905c1eb Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 13 Mar 2026 11:05:27 +0100 Subject: [PATCH 09/28] appease mypy --- fastapi/_compat/v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index f98013a0b932f..732ed9eca78c5 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -43,7 +43,7 @@ try: from pydantic_extra_types.color import Color except ImportError: - from pydantic.color import Color # noqa: F401 + from pydantic.color import Color # type: ignore[assignment] # noqa: F401 def define_forwardref() -> Callable[..., Any]: From 6851dad91c090875955c44be8da11f5d3d8ec370 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 13 Mar 2026 11:10:05 +0100 Subject: [PATCH 10/28] appease mypy more --- fastapi/_compat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index 225e79d9f4485..a0bede3471d4b 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -19,7 +19,7 @@ from .shared import lenient_issubclass as lenient_issubclass from .shared import sequence_types as sequence_types from .shared import value_is_sequence as value_is_sequence -from .v2 import Color as Color +from .v2 import Color as Color # type: ignore[attr-defined] from .v2 import ModelField as ModelField from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError from .v2 import RequiredParam as RequiredParam From f57b166f56b8196062d388df466b0de000ccec53 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 13 Mar 2026 11:20:16 +0100 Subject: [PATCH 11/28] add pragma no cover again --- fastapi/_compat/v2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 732ed9eca78c5..9c6462dbfcd5b 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -43,7 +43,9 @@ try: from pydantic_extra_types.color import Color except ImportError: - from pydantic.color import Color # type: ignore[assignment] # noqa: F401 + from pydantic.color import ( + Color, # type: ignore[assignment] # noqa: F401 # pragma: no cover + ) def define_forwardref() -> Callable[..., Any]: From 926156f36076353a869c35c2f3cbece7478f6186 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 13 Mar 2026 11:25:55 +0100 Subject: [PATCH 12/28] appease mypy once more --- fastapi/_compat/v2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 9c6462dbfcd5b..207dbe16b53e2 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -43,8 +43,8 @@ try: from pydantic_extra_types.color import Color except ImportError: - from pydantic.color import ( - Color, # type: ignore[assignment] # noqa: F401 # pragma: no cover + from pydantic.color import ( # type: ignore[assignment] + Color, # noqa: F401 # pragma: no cover ) From 4f4491a14ea236c41a70fb94581322f42605b7be Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 13 Mar 2026 11:31:01 +0100 Subject: [PATCH 13/28] more pragma omg --- fastapi/_compat/v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 207dbe16b53e2..a02a989079ae8 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -43,7 +43,7 @@ try: from pydantic_extra_types.color import Color except ImportError: - from pydantic.color import ( # type: ignore[assignment] + from pydantic.color import ( # type: ignore[assignment] # pragma: no cover Color, # noqa: F401 # pragma: no cover ) From 1f676f1889a5c5f27472f400336f5e8c6762232a Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 13 Mar 2026 11:50:30 +0100 Subject: [PATCH 14/28] cleanup --- fastapi/_compat/v2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index a02a989079ae8..0a83934752e1a 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -42,9 +42,9 @@ # pydantic.color.Color is deprecated since v2.0b3 try: from pydantic_extra_types.color import Color -except ImportError: - from pydantic.color import ( # type: ignore[assignment] # pragma: no cover - Color, # noqa: F401 # pragma: no cover +except ImportError: # pragma: no cover + from pydantic.color import ( # type: ignore[assignment] + Color, # noqa: F401 ) From f3481b0605de8cc01b92d8a41759614a2ddd42fc Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Mar 2026 12:29:48 +0100 Subject: [PATCH 15/28] remove unused ty ignores --- fastapi/_compat/__init__.py | 2 +- fastapi/dependencies/utils.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index 42a4aca628dc4..a0bede3471d4b 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -27,7 +27,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/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 From 6c76442f2ba27b31335ad091877a0e17ad245153 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Mar 2026 14:37:20 +0100 Subject: [PATCH 16/28] support both Color classes in ENCODERS_BY_TYPE --- fastapi/_compat/__init__.py | 1 - fastapi/_compat/v2.py | 12 ++---------- fastapi/encoders.py | 20 ++++++++++++++++++-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index a0bede3471d4b..4581c38c88ede 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -19,7 +19,6 @@ from .shared import lenient_issubclass as lenient_issubclass from .shared import sequence_types as sequence_types from .shared import value_is_sequence as value_is_sequence -from .v2 import Color as Color # type: ignore[attr-defined] from .v2 import ModelField as ModelField from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError from .v2 import RequiredParam as RequiredParam diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 8587393ea4bf7..9dad514b95023 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -23,7 +23,7 @@ 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] +from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-defined] # ty: ignore[unused-ignore-comment] GetJsonSchemaHandler as GetJsonSchemaHandler, ) from pydantic.fields import FieldInfo as FieldInfo @@ -39,21 +39,13 @@ RequiredParam = PydanticUndefined Undefined = PydanticUndefined -# pydantic.color.Color is deprecated since v2.0b3 -try: - from pydantic_extra_types.color import Color -except ImportError: # pragma: no cover - from pydantic.color import ( # type: ignore[assignment] - Color, # noqa: F401 - ) - def define_forwardref() -> Callable[..., 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 lambda *args: try_eval_type(*args)[0] - return _pydantic_typing_extra.eval_type_lenient # pragma: no cover + return _pydantic_typing_extra.eval_type_lenient # pragma: no cover # ty: ignore[deprecated] evaluate_forwardref = define_forwardref() diff --git a/fastapi/encoders.py b/fastapi/encoders.py index 7c46072041e29..d34cd009768d8 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -27,11 +27,26 @@ from pydantic_core import PydanticUndefinedType from ._compat import ( - Color, Url, is_pydantic_v1_model_instance, ) +try: + 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 + + +# pydantic.color.Color is deprecated since v2.0b3 -> supporting the new one as well +try: + 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 +82,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, From f6c69a57b0c31bf8f7a9dfba430a8dcb5123f3cd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 13:38:22 +0000 Subject: [PATCH 17/28] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat/v2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 9dad514b95023..89340fcf394a6 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -45,7 +45,9 @@ def define_forwardref() -> Callable[..., Any]: try_eval_type = getattr(_pydantic_typing_extra, "try_eval_type", None) if try_eval_type is not None: return lambda *args: try_eval_type(*args)[0] - return _pydantic_typing_extra.eval_type_lenient # pragma: no cover # ty: ignore[deprecated] + return ( + _pydantic_typing_extra.eval_type_lenient + ) # pragma: no cover # ty: ignore[deprecated] evaluate_forwardref = define_forwardref() From 6646cb49398227766dfa0721eb316f4ab930055f Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Mar 2026 15:20:48 +0100 Subject: [PATCH 18/28] add tests for both Color types --- fastapi/encoders.py | 3 ++- tests/test_jsonable_encoder.py | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/fastapi/encoders.py b/fastapi/encoders.py index d34cd009768d8..43f24101b6f8b 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -32,6 +32,7 @@ ) 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 @@ -39,8 +40,8 @@ class Color: # type: ignore[no-redef] # ty: ignore[unused-ignore-comment] pass -# pydantic.color.Color is deprecated since v2.0b3 -> supporting the new one as well 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 diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index 595202beaf371..c92cd8c7b876a 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -311,3 +311,38 @@ class Model(BaseModel): def test_encode_pydantic_undefined(): data = {"value": Undefined} assert jsonable_encoder(data) == {"value": None} + + +@pytest.mark.filterwarnings("ignore::pydantic.warnings.PydanticDeprecatedSince20") +def test_deprecated_color(): + try: + from pydantic.color import Color + # from pydantic_extra_types.color import Color as ExtraColor + + class ModelWithColors(BaseModel): + color: Color + model_config = {"arbitrary_types_allowed": True} + + obj = ModelWithColors(color=Color("blue")) + encoded = jsonable_encoder(obj) + assert encoded == {"color": "blue"} + + except ImportError: + pass + +def test_new_color(): + try: + from pydantic_extra_types.color import Color + + class ModelWithColors(BaseModel): + color: Color + model_config = {"arbitrary_types_allowed": True} + + obj = ModelWithColors(color=Color("blue")) + encoded = jsonable_encoder(obj) + assert encoded == {"color": "blue"} + + except ImportError: + pass + + From f7c8b7c40ff0b28112813f73ba443fe061f273b3 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Mar 2026 15:31:26 +0100 Subject: [PATCH 19/28] one parametrized method instead of two --- tests/test_jsonable_encoder.py | 50 ++++++++++++++-------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index c92cd8c7b876a..ec449f5b68cff 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -313,36 +313,28 @@ def test_encode_pydantic_undefined(): assert jsonable_encoder(data) == {"value": None} -@pytest.mark.filterwarnings("ignore::pydantic.warnings.PydanticDeprecatedSince20") -def test_deprecated_color(): +@pytest.mark.parametrize( + "module_path", + [ + pytest.param( + "pydantic.color", + marks=pytest.mark.filterwarnings( + "ignore::pydantic.warnings.PydanticDeprecatedSince20" + ), + ), + pytest.param("pydantic_extra_types.color"), + ], +) +def test_color_encoder(module_path): try: - from pydantic.color import Color - # from pydantic_extra_types.color import Color as ExtraColor - - class ModelWithColors(BaseModel): - color: Color - model_config = {"arbitrary_types_allowed": True} - - obj = ModelWithColors(color=Color("blue")) - encoded = jsonable_encoder(obj) - assert encoded == {"color": "blue"} - - except ImportError: - pass - -def test_new_color(): - try: - from pydantic_extra_types.color import Color - - class ModelWithColors(BaseModel): - color: Color - model_config = {"arbitrary_types_allowed": True} - - obj = ModelWithColors(color=Color("blue")) - encoded = jsonable_encoder(obj) - assert encoded == {"color": "blue"} - + Color = __import__(module_path, fromlist=["Color"]).Color except ImportError: - pass + pytest.skip(f"{module_path} not available") + class ModelWithColors(BaseModel): + color: Color + model_config = {"arbitrary_types_allowed": True} + obj = ModelWithColors(color=Color("blue")) + encoded = jsonable_encoder(obj) + assert encoded == {"color": "blue"} From 49cb3869dcd73862d1ea55dcb037778c6efe60b5 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Mar 2026 15:39:40 +0100 Subject: [PATCH 20/28] more generic deprecation warning --- tests/test_jsonable_encoder.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index ec449f5b68cff..01a7b6f35e16d 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -313,15 +313,11 @@ def test_encode_pydantic_undefined(): assert jsonable_encoder(data) == {"value": None} +@pytest.mark.filterwarnings("ignore::DeprecationWarning") @pytest.mark.parametrize( "module_path", [ - pytest.param( - "pydantic.color", - marks=pytest.mark.filterwarnings( - "ignore::pydantic.warnings.PydanticDeprecatedSince20" - ), - ), + pytest.param("pydantic.color"), pytest.param("pydantic_extra_types.color"), ], ) From c4127224fd0dfc524f2c5ed77f0d30b3bbe7b095 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Mar 2026 15:45:06 +0100 Subject: [PATCH 21/28] simplify test, ensure it fails on master --- tests/test_jsonable_encoder.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index 01a7b6f35e16d..dfbf478c4df43 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -321,16 +321,11 @@ def test_encode_pydantic_undefined(): pytest.param("pydantic_extra_types.color"), ], ) -def test_color_encoder(module_path): +def test_encode_color(module_path): try: Color = __import__(module_path, fromlist=["Color"]).Color except ImportError: pytest.skip(f"{module_path} not available") - class ModelWithColors(BaseModel): - color: Color - model_config = {"arbitrary_types_allowed": True} - - obj = ModelWithColors(color=Color("blue")) - encoded = jsonable_encoder(obj) - assert encoded == {"color": "blue"} + data = {"color": Color("blue")} + assert jsonable_encoder(data) == {"color": "blue"} From 53a6ee41862fccd7c51e9757626d2ff9646cad1f Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Mar 2026 15:51:15 +0100 Subject: [PATCH 22/28] add pragma no cover --- tests/test_jsonable_encoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index dfbf478c4df43..c23a9e5d7918a 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -324,7 +324,7 @@ def test_encode_pydantic_undefined(): def test_encode_color(module_path): try: Color = __import__(module_path, fromlist=["Color"]).Color - except ImportError: + except ImportError: # pragma: no cover pytest.skip(f"{module_path} not available") data = {"color": Color("blue")} From 33b5930c3b0ff9a9f47b387691402640778f317f Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Mar 2026 16:19:16 +0100 Subject: [PATCH 23/28] add explicit test for lowest supported pydantic version --- .github/workflows/test.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2e39633a0c58a..b9f881eac38b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,6 +52,8 @@ jobs: python-version: [ "3.14" ] uv-resolution: - highest + pydantic-version: + - latest starlette-src: - starlette-pypi - starlette-git @@ -60,22 +62,27 @@ jobs: python-version: "3.10" coverage: coverage uv-resolution: lowest-direct + pydantic-version: lowest - os: windows-latest python-version: "3.12" coverage: coverage uv-resolution: lowest-direct + pydantic-version: lowest - os: ubuntu-latest python-version: "3.13" coverage: coverage uv-resolution: highest + pydantic-version: latest - os: ubuntu-latest python-version: "3.13" uv-resolution: highest + pydantic-version: latest codspeed: codspeed - os: ubuntu-latest python-version: "3.14" coverage: coverage uv-resolution: highest + pydantic-version: latest starlette-src: starlette-git fail-fast: false runs-on: ${{ matrix.os }} @@ -102,6 +109,9 @@ jobs: uv.lock - name: Install Dependencies run: uv sync --no-dev --group tests --extra all + - name: Pin lowest supported Pydantic version + if: matrix.pydantic-version == 'lowest' + run: uv pip install "pydantic==2.7.0" - name: Install Starlette from source if: matrix.starlette-src == 'starlette-git' run: uv pip install "git+https://github.com/Kludex/starlette@main" From 381f567a1a51cc0ec01311ebf879c06feeb8b511 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 17 Mar 2026 17:54:34 +0100 Subject: [PATCH 24/28] Revert "add explicit test for lowest supported pydantic version" This reverts commit 33b5930c3b0ff9a9f47b387691402640778f317f. --- .github/workflows/test.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b9f881eac38b5..2e39633a0c58a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,8 +52,6 @@ jobs: python-version: [ "3.14" ] uv-resolution: - highest - pydantic-version: - - latest starlette-src: - starlette-pypi - starlette-git @@ -62,27 +60,22 @@ jobs: python-version: "3.10" coverage: coverage uv-resolution: lowest-direct - pydantic-version: lowest - os: windows-latest python-version: "3.12" coverage: coverage uv-resolution: lowest-direct - pydantic-version: lowest - os: ubuntu-latest python-version: "3.13" coverage: coverage uv-resolution: highest - pydantic-version: latest - os: ubuntu-latest python-version: "3.13" uv-resolution: highest - pydantic-version: latest codspeed: codspeed - os: ubuntu-latest python-version: "3.14" coverage: coverage uv-resolution: highest - pydantic-version: latest starlette-src: starlette-git fail-fast: false runs-on: ${{ matrix.os }} @@ -109,9 +102,6 @@ jobs: uv.lock - name: Install Dependencies run: uv sync --no-dev --group tests --extra all - - name: Pin lowest supported Pydantic version - if: matrix.pydantic-version == 'lowest' - run: uv pip install "pydantic==2.7.0" - name: Install Starlette from source if: matrix.starlette-src == 'starlette-git' run: uv pip install "git+https://github.com/Kludex/starlette@main" From 5460dcc5a24d6f4bfbacdd1494fad2f537e127f9 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 18 Mar 2026 12:01:38 +0100 Subject: [PATCH 25/28] add correct typing information --- fastapi/_compat/v2.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 89340fcf394a6..a980f55879409 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -1,6 +1,6 @@ import re import warnings -from collections.abc import Callable, Sequence +from collections.abc import Sequence from copy import copy from dataclasses import dataclass, is_dataclass from enum import Enum @@ -40,17 +40,18 @@ Undefined = PydanticUndefined -def define_forwardref() -> Callable[..., Any]: +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 lambda *args: try_eval_type(*args)[0] - return ( - _pydantic_typing_extra.eval_type_lenient - ) # pragma: no cover # ty: ignore[deprecated] - - -evaluate_forwardref = define_forwardref() + return try_eval_type(value, globalns, localns)[0] + return _pydantic_typing_extra.eval_type_lenient( # ty: ignore[deprecated] + value, globalns, localns + ) # pragma: no cover class GenerateJsonSchema(_GenerateJsonSchema): From 40fdef60dced0fb2464e37438b28af35933b0a78 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Mon, 23 Mar 2026 13:55:37 +0100 Subject: [PATCH 26/28] remove pragma no cover as the test suite now runs on Pydantic 2.9.0 --- fastapi/_compat/v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index a980f55879409..3b64fba76c900 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -51,7 +51,7 @@ def evaluate_forwardref( return try_eval_type(value, globalns, localns)[0] return _pydantic_typing_extra.eval_type_lenient( # ty: ignore[deprecated] value, globalns, localns - ) # pragma: no cover + ) class GenerateJsonSchema(_GenerateJsonSchema): From 833ff83892f848da02cddbb1095a920018ce0a31 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Mon, 23 Mar 2026 14:02:19 +0100 Subject: [PATCH 27/28] try bumping Pydantic to 2.11.0 --- .github/workflows/test.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e852e0f25d3b0..bcda3781241b8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -104,7 +104,7 @@ jobs: run: uv sync --no-dev --group tests --extra all - name: Ensure that we have the lowest supported Pydantic version if: matrix.uv-resolution == 'lowest-direct' - run: uv pip install "pydantic==2.9.0" + run: uv pip install "pydantic==2.11.0" - name: Install Starlette from source if: matrix.starlette-src == 'starlette-git' run: uv pip install "git+https://github.com/Kludex/starlette@main" diff --git a/pyproject.toml b/pyproject.toml index 612d8a0d8abd1..4c4fb2fb25114 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ classifiers = [ ] dependencies = [ "starlette>=0.46.0", - "pydantic>=2.9.0", + "pydantic>=2.11.0", "typing-extensions>=4.8.0", "typing-inspection>=0.4.2", "annotated-doc>=0.0.2", From e0e25685acdda24e2a36faf7d435c50295fe0807 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Mon, 23 Mar 2026 14:04:58 +0100 Subject: [PATCH 28/28] revert commit that was meant for a different branch --- .github/workflows/test.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bcda3781241b8..e852e0f25d3b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -104,7 +104,7 @@ jobs: run: uv sync --no-dev --group tests --extra all - name: Ensure that we have the lowest supported Pydantic version if: matrix.uv-resolution == 'lowest-direct' - run: uv pip install "pydantic==2.11.0" + run: uv pip install "pydantic==2.9.0" - name: Install Starlette from source if: matrix.starlette-src == 'starlette-git' run: uv pip install "git+https://github.com/Kludex/starlette@main" diff --git a/pyproject.toml b/pyproject.toml index 4c4fb2fb25114..612d8a0d8abd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ classifiers = [ ] dependencies = [ "starlette>=0.46.0", - "pydantic>=2.11.0", + "pydantic>=2.9.0", "typing-extensions>=4.8.0", "typing-inspection>=0.4.2", "annotated-doc>=0.0.2",