diff --git a/django/template/base.py b/django/template/base.py index 5e541c3960..ca4202391a 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -65,6 +65,7 @@ from django.utils.safestring import SafeData, SafeString, mark_safe from django.utils.text import get_text_list, smart_split, unescape_string_literal from django.utils.timezone import template_localtime from django.utils.translation import gettext_lazy, pgettext_lazy +from django.utils.version import PY314 from .exceptions import TemplateSyntaxError @@ -92,6 +93,9 @@ tag_re = re.compile(r"({%.*?%}|{{.*?}}|{#.*?#})") logger = logging.getLogger("django.template") +if PY314: + import annotationlib + class TokenType(Enum): TEXT = 0 @@ -825,9 +829,19 @@ class FilterExpression: # Check to see if a decorator is providing the real function. func = inspect.unwrap(func) - args, _, _, defaults, _, _, _ = inspect.getfullargspec(func) - alen = len(args) - dlen = len(defaults or []) + if PY314: + sig = inspect.signature( + func, annotation_format=annotationlib.Format.FORWARDREF + ) + else: + sig = inspect.signature(func) + alen = len(sig.parameters) + + non_default_params = [ + p for p in sig.parameters.values() if p.default is p.empty + ] + dlen = alen - len(non_default_params) + # Not enough OR Too many if plen < (alen - dlen) or plen > alen: raise TemplateSyntaxError( diff --git a/tests/template_tests/test_parser.py b/tests/template_tests/test_parser.py index 317fb88238..e5dbfb92d6 100644 --- a/tests/template_tests/test_parser.py +++ b/tests/template_tests/test_parser.py @@ -3,6 +3,9 @@ Testing some internals of the template processing. These are *not* examples to be copied in user code. """ +import unittest +from typing import TYPE_CHECKING + from django.template import Library, TemplateSyntaxError from django.template.base import ( FilterExpression, @@ -15,6 +18,10 @@ from django.template.base import ( ) from django.template.defaultfilters import register as filter_library from django.test import SimpleTestCase +from django.utils.version import PY314 + +if TYPE_CHECKING: + from django.utils.safestring import SafeText class ParserTests(SimpleTestCase): @@ -240,3 +247,17 @@ class ParserTests(SimpleTestCase): FilterExpression(num, p).resolve({}) with self.assertRaises(TemplateSyntaxError): FilterExpression(f"0|default:{num}", p).resolve({}) + + @unittest.skipUnless(PY314, "Deferred annotations are Python 3.14+ only") + def test_register_filter_deferred_annotations(self): + register = Library() + + @register.filter("example") + def example_filter(value: str, arg: str = "default") -> SafeText: + return f"{value}_{arg}" + + result = FilterExpression.args_check( + "example", example_filter, ["extra_example"] + ) + + self.assertTrue(result)