File python-pydantic-annotations.patch of Package python-pydantic
From 024e2929dd69f0e746d7df4a2e26ca67025a8d79 Mon Sep 17 00:00:00 2001
From: Victorien <65306057+Viicos@users.noreply.github.com>
Date: Wed, 8 Oct 2025 16:10:54 +0200
Subject: [PATCH] Do not evaluate annotations when inspecting validators and
serializers (#12355)
---
pydantic/_internal/_decorators.py | 23 +++++++++++++-----
tests/test_deferred_annotations.py | 38 +++++++++++++++++++++++++++++-
2 files changed, 54 insertions(+), 7 deletions(-)
diff --git a/pydantic/_internal/_decorators.py b/pydantic/_internal/_decorators.py
index 36d0642fa..1c48ad1e9 100644
--- a/pydantic/_internal/_decorators.py
+++ b/pydantic/_internal/_decorators.py
@@ -2,6 +2,7 @@
from __future__ import annotations as _annotations
+import sys
import types
from collections import deque
from collections.abc import Iterable
@@ -534,7 +535,7 @@ def inspect_validator(validator: Callable[..., Any], mode: FieldValidatorModes)
Whether the validator takes an info argument.
"""
try:
- sig = signature(validator)
+ sig = _signature_no_eval(validator)
except (ValueError, TypeError):
# `inspect.signature` might not be able to infer a signature, e.g. with C objects.
# In this case, we assume no info argument is present:
@@ -572,7 +573,7 @@ def inspect_field_serializer(serializer: Callable[..., Any], mode: Literal['plai
Tuple of (is_field_serializer, info_arg).
"""
try:
- sig = signature(serializer)
+ sig = _signature_no_eval(serializer)
except (ValueError, TypeError):
# `inspect.signature` might not be able to infer a signature, e.g. with C objects.
# In this case, we assume no info argument is present and this is not a method:
@@ -610,7 +611,7 @@ def inspect_annotated_serializer(serializer: Callable[..., Any], mode: Literal['
info_arg
"""
try:
- sig = signature(serializer)
+ sig = _signature_no_eval(serializer)
except (ValueError, TypeError):
# `inspect.signature` might not be able to infer a signature, e.g. with C objects.
# In this case, we assume no info argument is present:
@@ -642,7 +643,7 @@ def inspect_model_serializer(serializer: Callable[..., Any], mode: Literal['plai
'`@model_serializer` must be applied to instance methods', code='model-serializer-instance-method'
)
- sig = signature(serializer)
+ sig = _signature_no_eval(serializer)
info_arg = _serializer_info_arg(mode, count_positional_required_params(sig))
if info_arg is None:
raise PydanticUserError(
@@ -690,7 +691,7 @@ def is_instance_method_from_sig(function: AnyDecoratorCallable) -> bool:
Returns:
`True` if the function is an instance method, `False` otherwise.
"""
- sig = signature(unwrap_wrapped_function(function))
+ sig = _signature_no_eval(unwrap_wrapped_function(function))
first = next(iter(sig.parameters.values()), None)
if first and first.name == 'self':
return True
@@ -714,7 +715,7 @@ def ensure_classmethod_based_on_signature(function: AnyDecoratorCallable) -> Any
def _is_classmethod_from_sig(function: AnyDecoratorCallable) -> bool:
- sig = signature(unwrap_wrapped_function(function))
+ sig = _signature_no_eval(unwrap_wrapped_function(function))
first = next(iter(sig.parameters.values()), None)
if first and first.name == 'cls':
return True
@@ -842,3 +843,13 @@ def ensure_property(f: Any) -> Any:
return f
else:
return property(f)
+
+
+def _signature_no_eval(f: Callable[..., Any]) -> Signature:
+ """Get the signature of a callable without evaluating any annotations."""
+ if sys.version_info >= (3, 14):
+ from annotationlib import Format
+
+ return signature(f, annotation_format=Format.FORWARDREF)
+ else:
+ return signature(f)
diff --git a/tests/test_deferred_annotations.py b/tests/test_deferred_annotations.py
index 1b2ae48e5..c6408d100 100644
--- a/tests/test_deferred_annotations.py
+++ b/tests/test_deferred_annotations.py
@@ -7,7 +7,15 @@ from typing import Annotated
import pytest
from annotated_types import MaxLen
-from pydantic import BaseModel, Field, ValidationError
+from pydantic import (
+ BaseModel,
+ Field,
+ ValidationError,
+ field_serializer,
+ field_validator,
+ model_serializer,
+ model_validator,
+)
from pydantic.dataclasses import dataclass
pytestmark = pytest.mark.skipif(
@@ -80,3 +88,31 @@ def test_deferred_annotations_pydantic_dataclass_pydantic_field() -> None:
Int = int
assert A(a='1').a == 1
+
+
+def test_deferred_annotations_return_values() -> None:
+ class Model(BaseModel):
+ a: int
+
+ @model_validator(mode='after')
+ def check(self) -> Model:
+ return self
+
+ @model_validator(mode='before')
+ def before(cls, data) -> MyDict:
+ return data
+
+ @model_serializer(mode='plain')
+ def ser(self) -> MyDict:
+ return {'a': self.a}
+
+ @field_validator('a', mode='before')
+ def validate_a(cls, v) -> MyInt:
+ return v
+
+ @field_serializer('a', mode='plain')
+ def serialize_a(self, v) -> MyInt:
+ return v
+
+ MyDict = dict
+ MyInt = int
--
2.51.0