Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5fe7833
use Pydantic's try_eval_type if available (otherwise deprecation warn…
svlandeg Mar 12, 2026
94bd713
🎨 Auto format
pre-commit-ci-lite[bot] Mar 12, 2026
a1abbce
assert evaluate_forwardref is not None
svlandeg Mar 12, 2026
97ce5de
make mypy happy
svlandeg Mar 12, 2026
8e5d327
fix return type of eval_type
svlandeg Mar 13, 2026
2d2fe3e
add no cover for now
svlandeg Mar 13, 2026
12f3db5
Merge branch 'master' into feat/depr
svlandeg Mar 13, 2026
bbf1529
coditional import of Color to avoid deprecation warning
svlandeg Mar 13, 2026
171f062
fix import
svlandeg Mar 13, 2026
e867534
appease mypy
svlandeg Mar 13, 2026
6851dad
appease mypy more
svlandeg Mar 13, 2026
f57b166
add pragma no cover again
svlandeg Mar 13, 2026
926156f
appease mypy once more
svlandeg Mar 13, 2026
4f4491a
more pragma omg
svlandeg Mar 13, 2026
1f676f1
cleanup
svlandeg Mar 13, 2026
987f1d9
Merge branch 'master' into feat/depr
svlandeg Mar 16, 2026
76f8b96
Merge branch 'master' into feat/depr
svlandeg Mar 17, 2026
f3481b0
remove unused ty ignores
svlandeg Mar 17, 2026
6c76442
support both Color classes in ENCODERS_BY_TYPE
svlandeg Mar 17, 2026
f6c69a5
🎨 Auto format
pre-commit-ci-lite[bot] Mar 17, 2026
6646cb4
add tests for both Color types
svlandeg Mar 17, 2026
f7c8b7c
one parametrized method instead of two
svlandeg Mar 17, 2026
49cb386
more generic deprecation warning
svlandeg Mar 17, 2026
c412722
simplify test, ensure it fails on master
svlandeg Mar 17, 2026
53a6ee4
add pragma no cover
svlandeg Mar 17, 2026
33b5930
add explicit test for lowest supported pydantic version
svlandeg Mar 17, 2026
381f567
Revert "add explicit test for lowest supported pydantic version"
svlandeg Mar 17, 2026
5460dcc
add correct typing information
svlandeg Mar 18, 2026
d85b3dc
Merge branch 'master' into feat/depr
svlandeg Mar 18, 2026
548d002
Merge branch 'master' into feat/depr
svlandeg Mar 20, 2026
ed38f86
Merge branch 'master' into feat/depr
svlandeg Mar 23, 2026
40fdef6
remove pragma no cover as the test suite now runs on Pydantic 2.9.0
svlandeg Mar 23, 2026
833ff83
try bumping Pydantic to 2.11.0
svlandeg Mar 23, 2026
e0e2568
revert commit that was meant for a different branch
svlandeg Mar 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fastapi/_compat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 15 additions & 2 deletions fastapi/_compat/v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an alternative, we can use try...except:

    try:    
        return _pydantic_typing_extra.try_eval_type(value, globalns, localns)[0]
    except AttrbuteError:
        return _pydantic_typing_extra.eval_type_lenient(  # ty: ignore[deprecated]
            value, globalns, localns
        )  # pragma: no cover

Or move this to module level, and only use it inside evaluate_forwardref (so that evaluate_forwardref would make it typed).

It might also be a bit faster (but it's only used at app stratup, so maybe not as important)

Copy link
Copy Markdown
Member Author

@svlandeg svlandeg Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I think it's a style preference, I'm personally more fan of checking explicitely first rather than relying on a try-except, but both are valid options. I'll leave it up to Sebastián to decide if he's got a preference 🙂

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found it a bit unfortunate that this PR still relies on an internal function.. The only reason I kept eval_type_lenient() in Pydantic was because FastAPI was using it, and now the same issue exists with try_eval_type().

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Viicos: right, good point. What would be the better way to address this? I can make a new PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be worth solving this cleanly, that is by first having the ability to do such evaluations using the stdlib. I proposed https://discuss.python.org/t/100698 a while ago, I'll try to continue working on it. Then we could see how can this be used in FastAPI/Pydantic!

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):
Expand Down
4 changes: 2 additions & 2 deletions fastapi/dependencies/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
21 changes: 19 additions & 2 deletions fastapi/encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
18 changes: 18 additions & 0 deletions tests/test_jsonable_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For pydantic_extra_types.color, this will fail on master with

ValueError: [TypeError("'Color' object is not iterable"), TypeError('vars() argument must have dict attribute')]

Copy link
Copy Markdown
Member

@YuriiMotov YuriiMotov Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For pydantic_extra_types.color, this will fail on master with

ValueError: [TypeError("'Color' object is not iterable"), TypeError('vars() argument must have dict attribute')]

I took me some time to understand this comment 😅

Just to clarify if somebody also gets confused: it fails on master because pydantic_extra_types.color.Color is not currently supported by jsonable_encoder. This PR adds such support

],
)
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