diff --git a/django/utils/inspect.py b/django/utils/inspect.py index 31f3cf994b..ac5e82ca14 100644 --- a/django/utils/inspect.py +++ b/django/utils/inspect.py @@ -1,10 +1,24 @@ import functools import inspect +from django.utils.version import PY314 + +if PY314: + import annotationlib + @functools.lru_cache(maxsize=512) def _get_func_parameters(func, remove_first): - parameters = tuple(inspect.signature(func).parameters.values()) + # As the annotations are not used in any case, inspect the signature with + # FORWARDREF to leave any deferred annotations unevaluated. + if PY314: + signature = inspect.signature( + func, annotation_format=annotationlib.Format.FORWARDREF + ) + else: + signature = inspect.signature(func) + + parameters = tuple(signature.parameters.values()) if remove_first: parameters = parameters[1:] return parameters diff --git a/tests/utils_tests/test_inspect.py b/tests/utils_tests/test_inspect.py index 38ea35ecfb..f6e82e5808 100644 --- a/tests/utils_tests/test_inspect.py +++ b/tests/utils_tests/test_inspect.py @@ -1,8 +1,13 @@ import subprocess import unittest +from typing import TYPE_CHECKING from django.shortcuts import aget_object_or_404 from django.utils import inspect +from django.utils.version import PY314 + +if TYPE_CHECKING: + from django.utils.safestring import SafeString class Person: @@ -103,6 +108,16 @@ class TestInspectMethods(unittest.TestCase): self.assertIs(inspect.func_accepts_kwargs(Person.all_kinds), True) self.assertIs(inspect.func_accepts_kwargs(Person().just_args), False) + @unittest.skipUnless(PY314, "Deferred annotations are Python 3.14+ only") + def test_func_accepts_kwargs_deferred_annotations(self): + + def func_with_annotations(self, name: str, complex: SafeString) -> None: + pass + + # Inspection fails with deferred annotations with python 3.14+. Earlier + # Python versions trigger the NameError on module initialization. + self.assertIs(inspect.func_accepts_kwargs(func_with_annotations), False) + class IsModuleLevelFunctionTestCase(unittest.TestCase): @classmethod