From fd6514eddc0776e27719ddd956247c08bd9b1831 Mon Sep 17 00:00:00 2001 From: Augusto Pontes Date: Sat, 8 Nov 2025 08:37:56 -0300 Subject: [PATCH 1/8] Adding inspect.signature to avoid type and value errors --- django/template/base.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index 5e541c3960..16ef7d517f 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -825,9 +825,20 @@ 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 []) + try: + # Using signature first(more modern) + 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) + except (TypeError, ValueError): + # If fails, use the old getfullargspec(deprecated) + args, _, _, defaults, _, _, _ = inspect.getfullargspec(func) + alen = len(args) + dlen = len(defaults or []) + # Not enough OR Too many if plen < (alen - dlen) or plen > alen: raise TemplateSyntaxError( From d03c80933aa07d1ba6a9bd585d7cde7e56a78632 Mon Sep 17 00:00:00 2001 From: Augusto Pontes Date: Sat, 8 Nov 2025 09:07:18 -0300 Subject: [PATCH 2/8] Fixing black formatter --- django/template/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index 16ef7d517f..fe8552b7ff 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -830,8 +830,9 @@ class FilterExpression: sig = inspect.signature(func) alen = len(sig.parameters) - non_default_params = [p for p in sig.parameters.values() - if p.default is p.empty] + non_default_params = [ + p for p in sig.parameters.values() if p.default is p.empty + ] dlen = alen - len(non_default_params) except (TypeError, ValueError): # If fails, use the old getfullargspec(deprecated) From 7d68cf8db0c7ddad420a106ebb5ce58a9d4f299b Mon Sep 17 00:00:00 2001 From: Augusto Pontes Date: Mon, 10 Nov 2025 17:46:06 -0300 Subject: [PATCH 3/8] Changed args_check parameters, Created class to verify deferred annotations only in python 3.14+ version --- django/template/base.py | 24 ++++++++++++------------ tests/template_tests/test_parser.py | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index fe8552b7ff..5e487a49ad 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,20 +829,16 @@ class FilterExpression: # Check to see if a decorator is providing the real function. func = inspect.unwrap(func) - try: - # Using signature first(more modern) + if PY314: + sig = inspect.signature(func, annotation_format=annotationlib.Format.FORWARDREF) + else: sig = inspect.signature(func) - alen = len(sig.parameters) + 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) - except (TypeError, ValueError): - # If fails, use the old getfullargspec(deprecated) - args, _, _, defaults, _, _, _ = inspect.getfullargspec(func) - alen = len(args) - dlen = len(defaults or []) + 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: diff --git a/tests/template_tests/test_parser.py b/tests/template_tests/test_parser.py index 317fb88238..18d7cfad2e 100644 --- a/tests/template_tests/test_parser.py +++ b/tests/template_tests/test_parser.py @@ -14,7 +14,16 @@ from django.template.base import ( VariableDoesNotExist, ) from django.template.defaultfilters import register as filter_library +from django.utils.version import PY314 from django.test import SimpleTestCase +from django.template import Library +from django.utils.html import escape + +from typing import TYPE_CHECKING +import unittest + +if TYPE_CHECKING: + from django.utils.safestring import SafeText class ParserTests(SimpleTestCase): @@ -240,3 +249,17 @@ class ParserTests(SimpleTestCase): FilterExpression(num, p).resolve({}) with self.assertRaises(TemplateSyntaxError): FilterExpression(f"0|default:{num}", p).resolve({}) + + +class FilterExpressionArgsTests(unittest.TestCase): + @unittest.skipUnless(PY314, "Deferred annotations area Python 3.14+ only") + def test_register_filter_deferred_annotations(self): + register = Library() + + @register.filter("example") + def example_filter(value: str) -> SafeText: + return escape(value) + + result = example_filter("example") + + self.assertTrue(result) From ed86951c509469aefef8353ebacbf0cebb4675f8 Mon Sep 17 00:00:00 2001 From: Augusto Pontes Date: Mon, 10 Nov 2025 17:56:36 -0300 Subject: [PATCH 4/8] Fixed black, isort, flake linters --- django/template/base.py | 4 +++- tests/template_tests/test_parser.py | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index 5e487a49ad..ca4202391a 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -830,7 +830,9 @@ class FilterExpression: func = inspect.unwrap(func) if PY314: - sig = inspect.signature(func, annotation_format=annotationlib.Format.FORWARDREF) + sig = inspect.signature( + func, annotation_format=annotationlib.Format.FORWARDREF + ) else: sig = inspect.signature(func) alen = len(sig.parameters) diff --git a/tests/template_tests/test_parser.py b/tests/template_tests/test_parser.py index 18d7cfad2e..1663061fd4 100644 --- a/tests/template_tests/test_parser.py +++ b/tests/template_tests/test_parser.py @@ -14,13 +14,12 @@ from django.template.base import ( VariableDoesNotExist, ) from django.template.defaultfilters import register as filter_library -from django.utils.version import PY314 from django.test import SimpleTestCase -from django.template import Library from django.utils.html import escape +from django.utils.version import PY314 -from typing import TYPE_CHECKING import unittest +from typing import TYPE_CHECKING if TYPE_CHECKING: from django.utils.safestring import SafeText From 8367c6352af1eb7f2c7ad0e2a8a19a3f5a832b09 Mon Sep 17 00:00:00 2001 From: Augusto Pontes Date: Mon, 10 Nov 2025 17:58:59 -0300 Subject: [PATCH 5/8] Fixed isort linter --- tests/template_tests/test_parser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/template_tests/test_parser.py b/tests/template_tests/test_parser.py index 1663061fd4..def95b3edc 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, @@ -18,9 +21,6 @@ from django.test import SimpleTestCase from django.utils.html import escape from django.utils.version import PY314 -import unittest -from typing import TYPE_CHECKING - if TYPE_CHECKING: from django.utils.safestring import SafeText From 78aa21ac9989ad0bf4378c470ecf32c171b4218b Mon Sep 17 00:00:00 2001 From: Augusto Pontes Date: Thu, 13 Nov 2025 14:30:59 -0300 Subject: [PATCH 6/8] Fixed testfiterexpression in test_parser --- tests/template_tests/test_parser.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/template_tests/test_parser.py b/tests/template_tests/test_parser.py index def95b3edc..67c24b6bca 100644 --- a/tests/template_tests/test_parser.py +++ b/tests/template_tests/test_parser.py @@ -250,15 +250,17 @@ class ParserTests(SimpleTestCase): FilterExpression(f"0|default:{num}", p).resolve({}) -class FilterExpressionArgsTests(unittest.TestCase): - @unittest.skipUnless(PY314, "Deferred annotations area Python 3.14+ only") +class FilterExpressionArgsTests(SimpleTestCase): + @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) -> SafeText: - return escape(value) + def example_filter(value: str, arg: str = "default") -> SafeText: + return f"{value}_{arg}" - result = example_filter("example") + result = FilterExpression.args_check( + "example", example_filter, ["extra_example"] + ) self.assertTrue(result) From c914b8b2092271903837cdbb969d5740a97c4e4d Mon Sep 17 00:00:00 2001 From: Augusto Pontes Date: Thu, 13 Nov 2025 14:34:39 -0300 Subject: [PATCH 7/8] Removed unused lib --- tests/template_tests/test_parser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/template_tests/test_parser.py b/tests/template_tests/test_parser.py index 67c24b6bca..4cfa846843 100644 --- a/tests/template_tests/test_parser.py +++ b/tests/template_tests/test_parser.py @@ -18,7 +18,6 @@ from django.template.base import ( ) from django.template.defaultfilters import register as filter_library from django.test import SimpleTestCase -from django.utils.html import escape from django.utils.version import PY314 if TYPE_CHECKING: From 8bef309e96882aac2594befe41d6d6434bbb06b5 Mon Sep 17 00:00:00 2001 From: Augusto Pontes Date: Fri, 14 Nov 2025 06:15:17 -0300 Subject: [PATCH 8/8] Moved test method to ParserTests --- tests/template_tests/test_parser.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/template_tests/test_parser.py b/tests/template_tests/test_parser.py index 4cfa846843..e5dbfb92d6 100644 --- a/tests/template_tests/test_parser.py +++ b/tests/template_tests/test_parser.py @@ -248,8 +248,6 @@ class ParserTests(SimpleTestCase): with self.assertRaises(TemplateSyntaxError): FilterExpression(f"0|default:{num}", p).resolve({}) - -class FilterExpressionArgsTests(SimpleTestCase): @unittest.skipUnless(PY314, "Deferred annotations are Python 3.14+ only") def test_register_filter_deferred_annotations(self): register = Library()