diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 9b922ed918..6561b3d63d 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -6,6 +6,7 @@ import inspect import math import re +import sys import uuid import warnings from collections.abc import Mapping @@ -58,6 +59,17 @@ class BuiltinSignatureError(Exception): pass +if sys.version_info >= (3, 14): + # Annotations are evaluated lazily since Python 3.14 (PEP 649), and + # inspect.signature() evaluates them, raising NameError for names only + # imported under TYPE_CHECKING. Only parameter kinds and defaults are + # needed in is_simple_callable(). See https://github.com/encode/django-rest-framework/discussions/9959 + import annotationlib + SIGNATURE_KWARGS = {'annotation_format': annotationlib.Format.FORWARDREF} +else: + SIGNATURE_KWARGS = {} + + def is_simple_callable(obj): """ True if the object is a callable that takes no arguments. @@ -74,7 +86,7 @@ def is_simple_callable(obj): if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)): return False - sig = inspect.signature(obj) + sig = inspect.signature(obj, **SIGNATURE_KWARGS) params = sig.parameters.values() return all( param.kind == param.VAR_POSITIONAL or diff --git a/tests/test_fields.py b/tests/test_fields.py index 4cbbc030e3..f52dd8e514 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -2,6 +2,7 @@ import math import os import re +import sys import uuid import warnings from decimal import ROUND_DOWN, ROUND_UP, Decimal @@ -121,6 +122,23 @@ def test_type_annotation(self): assert is_simple_callable(valid) + @pytest.mark.skipif(sys.version_info < (3, 14), reason="Annotations are evaluated lazily from Python 3.14") + def test_unresolvable_type_annotation(self): + # A name only imported under `if TYPE_CHECKING:` is undefined at runtime. + class Foo: + def valid(self) -> UndefinedType: # noqa: F821 + pass + + def valid(param: UndefinedType = None): # noqa: F821 + pass + + def invalid(param: UndefinedType): # noqa: F821 + pass + + assert is_simple_callable(Foo().valid) + assert is_simple_callable(valid) + assert not is_simple_callable(invalid) + # Tests for field keyword arguments and core functionality. # ---------------------------------------------------------