This commit is contained in:
d-lcw 2025-05-26 17:30:54 +08:00 committed by GitHub
commit 3ff25cbda5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 271 additions and 23 deletions

View File

@ -5,6 +5,7 @@ Unreleased
- Drop support for Python 3.9. :pr:`5730`
- Remove previously deprecated code: ``__version__``. :pr:`5648`
- Support for using @app.template_filter, @app.template_test, and @app.template_global decorators without parentheses. :pr:`5736`
Version 3.1.1

View File

@ -145,7 +145,11 @@ that. You can either put them by hand into the
:attr:`~flask.Flask.jinja_env` of the application or use the
:meth:`~flask.Flask.template_filter` decorator.
The two following examples work the same and both reverse an object::
The following examples work the same and all reverse an object::
@app.template_filter # use the function name as filter name
def reverse_filter(s):
return s[::-1]
@app.template_filter('reverse')
def reverse_filter(s):

View File

@ -662,20 +662,36 @@ class App(Scaffold):
@setupmethod
def template_filter(
self, name: str | None = None
) -> t.Callable[[T_template_filter], T_template_filter]:
self, name: t.Callable[..., t.Any] | str | None = None
) -> t.Callable[[T_template_filter], T_template_filter] | T_template_filter:
"""A decorator that is used to register custom template filter.
You can specify a name for the filter, otherwise the function
name will be used. Example::
@app.template_filter()
def reverse(s):
return s[::-1]
@app.template_filter()
def reverse(s):
return s[::-1]
The decorator also can be used without parentheses::
@app.template_filter
def reverse(s):
return s[::-1]
:param name: the optional name of the filter, otherwise the
function name will be used.
"""
if callable(name):
# If name is callable, it is the function to register.
# This is a shortcut for the common case of
# @app.template_filter
# def func():
func = name
self.add_template_filter(func)
return func
def decorator(f: T_template_filter) -> T_template_filter:
self.add_template_filter(f, name=name)
return f
@ -696,27 +712,47 @@ class App(Scaffold):
@setupmethod
def template_test(
self, name: str | None = None
) -> t.Callable[[T_template_test], T_template_test]:
self, name: t.Callable[..., t.Any] | str | None = None
) -> t.Callable[[T_template_test], T_template_test] | T_template_filter:
"""A decorator that is used to register custom template test.
You can specify a name for the test, otherwise the function
name will be used. Example::
@app.template_test()
def is_prime(n):
if n == 2:
return True
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
if n % i == 0:
return False
@app.template_test()
def is_prime(n):
if n == 2:
return True
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
if n % i == 0:
return False
return True
The decorator also can be used without parentheses::
@app.template_test
def is_prime(n):
if n == 2:
return True
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
if n % i == 0:
return False
.. versionadded:: 0.10
:param name: the optional name of the test, otherwise the
function name will be used.
"""
if callable(name):
# If name is callable, it is the function to register.
# This is a shortcut for the common case of
# @app.template_test
# def func():
func = name
self.add_template_test(func)
return func
def decorator(f: T_template_test) -> T_template_test:
self.add_template_test(f, name=name)
return f
@ -739,8 +775,8 @@ class App(Scaffold):
@setupmethod
def template_global(
self, name: str | None = None
) -> t.Callable[[T_template_global], T_template_global]:
self, name: t.Callable[..., t.Any] | str | None = None
) -> t.Callable[[T_template_global], T_template_global] | T_template_filter:
"""A decorator that is used to register a custom template global function.
You can specify a name for the global function, otherwise the function
name will be used. Example::
@ -749,12 +785,28 @@ class App(Scaffold):
def double(n):
return 2 * n
The decorator also can be used without parentheses::
@app.template_global
def double(n):
return 2 * n
.. versionadded:: 0.10
:param name: the optional name of the global function, otherwise the
function name will be used.
"""
if callable(name):
# If name is callable, it is the function to register.
# This is a shortcut for the common case of
# @app.template_global
# def func():
func = name
self.add_template_global(func)
return func
def decorator(f: T_template_global) -> T_template_global:
self.add_template_global(f, name=name)
return f

View File

@ -442,8 +442,8 @@ class Blueprint(Scaffold):
@setupmethod
def app_template_filter(
self, name: str | None = None
) -> t.Callable[[T_template_filter], T_template_filter]:
self, name: t.Callable[..., t.Any] | str | None = None
) -> t.Callable[[T_template_filter], T_template_filter] | T_template_filter:
"""Register a template filter, available in any template rendered by the
application. Equivalent to :meth:`.Flask.template_filter`.
@ -451,6 +451,16 @@ class Blueprint(Scaffold):
function name will be used.
"""
if callable(name):
# If name is callable, it is the function to register.
# This is a shortcut for the common case of
# @bp.add_template_filter
# def func():
func = name
self.add_app_template_filter(func)
return func
def decorator(f: T_template_filter) -> T_template_filter:
self.add_app_template_filter(f, name=name)
return f
@ -476,8 +486,8 @@ class Blueprint(Scaffold):
@setupmethod
def app_template_test(
self, name: str | None = None
) -> t.Callable[[T_template_test], T_template_test]:
self, name: t.Callable[..., t.Any] | str | None = None
) -> t.Callable[[T_template_test], T_template_test] | T_template_filter:
"""Register a template test, available in any template rendered by the
application. Equivalent to :meth:`.Flask.template_test`.
@ -487,6 +497,16 @@ class Blueprint(Scaffold):
function name will be used.
"""
if callable(name):
# If name is callable, it is the function to register.
# This is a shortcut for the common case of
# @bp.add_template_test
# def func():
func = name
self.add_app_template_test(func)
return func
def decorator(f: T_template_test) -> T_template_test:
self.add_app_template_test(f, name=name)
return f
@ -514,8 +534,8 @@ class Blueprint(Scaffold):
@setupmethod
def app_template_global(
self, name: str | None = None
) -> t.Callable[[T_template_global], T_template_global]:
self, name: t.Callable[..., t.Any] | str | None = None
) -> t.Callable[[T_template_global], T_template_global] | T_template_filter:
"""Register a template global, available in any template rendered by the
application. Equivalent to :meth:`.Flask.template_global`.
@ -524,6 +544,15 @@ class Blueprint(Scaffold):
:param name: the optional name of the global, otherwise the
function name will be used.
"""
if callable(name):
# If name is callable, it is the function to register.
# This is a shortcut for the common case of
# @bp.add_template_global
# def func():
func = name
self.add_app_template_global(func)
return func
def decorator(f: T_template_global) -> T_template_global:
self.add_app_template_global(f, name=name)

View File

@ -366,11 +366,35 @@ def test_template_filter(app):
def my_reverse(s):
return s[::-1]
@bp.app_template_filter
def my_reverse_2(s):
return s[::-1]
@bp.app_template_filter("my_reverse_custom_name_3")
def my_reverse_3(s):
return s[::-1]
@bp.app_template_filter(name="my_reverse_custom_name_4")
def my_reverse_4(s):
return s[::-1]
app.register_blueprint(bp, url_prefix="/py")
assert "my_reverse" in app.jinja_env.filters.keys()
assert app.jinja_env.filters["my_reverse"] == my_reverse
assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
assert "my_reverse_2" in app.jinja_env.filters.keys()
assert app.jinja_env.filters["my_reverse_2"] == my_reverse_2
assert app.jinja_env.filters["my_reverse_2"]("abcd") == "dcba"
assert "my_reverse_custom_name_3" in app.jinja_env.filters.keys()
assert app.jinja_env.filters["my_reverse_custom_name_3"] == my_reverse_3
assert app.jinja_env.filters["my_reverse_custom_name_3"]("abcd") == "dcba"
assert "my_reverse_custom_name_4" in app.jinja_env.filters.keys()
assert app.jinja_env.filters["my_reverse_custom_name_4"] == my_reverse_4
assert app.jinja_env.filters["my_reverse_custom_name_4"]("abcd") == "dcba"
def test_add_template_filter(app):
bp = flask.Blueprint("bp", __name__)
@ -502,11 +526,35 @@ def test_template_test(app):
def is_boolean(value):
return isinstance(value, bool)
@bp.app_template_test
def boolean_2(value):
return isinstance(value, bool)
@bp.app_template_test("my_boolean_custom_name")
def boolean_3(value):
return isinstance(value, bool)
@bp.app_template_test(name="my_boolean_custom_name_2")
def boolean_4(value):
return isinstance(value, bool)
app.register_blueprint(bp, url_prefix="/py")
assert "is_boolean" in app.jinja_env.tests.keys()
assert app.jinja_env.tests["is_boolean"] == is_boolean
assert app.jinja_env.tests["is_boolean"](False)
assert "boolean_2" in app.jinja_env.tests.keys()
assert app.jinja_env.tests["boolean_2"] == boolean_2
assert app.jinja_env.tests["boolean_2"](False)
assert "my_boolean_custom_name" in app.jinja_env.tests.keys()
assert app.jinja_env.tests["my_boolean_custom_name"] == boolean_3
assert app.jinja_env.tests["my_boolean_custom_name"](False)
assert "my_boolean_custom_name_2" in app.jinja_env.tests.keys()
assert app.jinja_env.tests["my_boolean_custom_name_2"] == boolean_4
assert app.jinja_env.tests["my_boolean_custom_name_2"](False)
def test_add_template_test(app):
bp = flask.Blueprint("bp", __name__)
@ -679,6 +727,18 @@ def test_template_global(app):
def get_answer():
return 42
@bp.app_template_global
def get_stuff_1():
return "get_stuff_1"
@bp.app_template_global("my_get_stuff_custom_name_2")
def get_stuff_2():
return "get_stuff_2"
@bp.app_template_global(name="my_get_stuff_custom_name_3")
def get_stuff_3():
return "get_stuff_3"
# Make sure the function is not in the jinja_env already
assert "get_answer" not in app.jinja_env.globals.keys()
app.register_blueprint(bp)
@ -688,10 +748,31 @@ def test_template_global(app):
assert app.jinja_env.globals["get_answer"] is get_answer
assert app.jinja_env.globals["get_answer"]() == 42
assert "get_stuff_1" in app.jinja_env.globals.keys()
assert app.jinja_env.globals["get_stuff_1"] == get_stuff_1
assert app.jinja_env.globals["get_stuff_1"](), "get_stuff_1"
assert "my_get_stuff_custom_name_2" in app.jinja_env.globals.keys()
assert app.jinja_env.globals["my_get_stuff_custom_name_2"] == get_stuff_2
assert app.jinja_env.globals["my_get_stuff_custom_name_2"](), "get_stuff_2"
assert "my_get_stuff_custom_name_3" in app.jinja_env.globals.keys()
assert app.jinja_env.globals["my_get_stuff_custom_name_3"] == get_stuff_3
assert app.jinja_env.globals["my_get_stuff_custom_name_3"](), "get_stuff_3"
with app.app_context():
rv = flask.render_template_string("{{ get_answer() }}")
assert rv == "42"
rv = flask.render_template_string("{{ get_stuff_1() }}")
assert rv == "get_stuff_1"
rv = flask.render_template_string("{{ my_get_stuff_custom_name_2() }}")
assert rv == "get_stuff_2"
rv = flask.render_template_string("{{ my_get_stuff_custom_name_3() }}")
assert rv == "get_stuff_3"
def test_request_processing(app, client):
bp = flask.Blueprint("bp", __name__)

View File

@ -129,6 +129,30 @@ def test_template_filter(app):
assert app.jinja_env.filters["my_reverse"] == my_reverse
assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
@app.template_filter
def my_reverse_2(s):
return s[::-1]
assert "my_reverse_2" in app.jinja_env.filters.keys()
assert app.jinja_env.filters["my_reverse_2"] == my_reverse_2
assert app.jinja_env.filters["my_reverse_2"]("abcd") == "dcba"
@app.template_filter("my_reverse_custom_name_3")
def my_reverse_3(s):
return s[::-1]
assert "my_reverse_custom_name_3" in app.jinja_env.filters.keys()
assert app.jinja_env.filters["my_reverse_custom_name_3"] == my_reverse_3
assert app.jinja_env.filters["my_reverse_custom_name_3"]("abcd") == "dcba"
@app.template_filter(name="my_reverse_custom_name_4")
def my_reverse_4(s):
return s[::-1]
assert "my_reverse_custom_name_4" in app.jinja_env.filters.keys()
assert app.jinja_env.filters["my_reverse_custom_name_4"] == my_reverse_4
assert app.jinja_env.filters["my_reverse_custom_name_4"]("abcd") == "dcba"
def test_add_template_filter(app):
def my_reverse(s):
@ -223,6 +247,30 @@ def test_template_test(app):
assert app.jinja_env.tests["boolean"] == boolean
assert app.jinja_env.tests["boolean"](False)
@app.template_test
def boolean_2(value):
return isinstance(value, bool)
assert "boolean_2" in app.jinja_env.tests.keys()
assert app.jinja_env.tests["boolean_2"] == boolean_2
assert app.jinja_env.tests["boolean_2"](False)
@app.template_test("my_boolean_custom_name")
def boolean_3(value):
return isinstance(value, bool)
assert "my_boolean_custom_name" in app.jinja_env.tests.keys()
assert app.jinja_env.tests["my_boolean_custom_name"] == boolean_3
assert app.jinja_env.tests["my_boolean_custom_name"](False)
@app.template_test(name="my_boolean_custom_name_2")
def boolean_4(value):
return isinstance(value, bool)
assert "my_boolean_custom_name_2" in app.jinja_env.tests.keys()
assert app.jinja_env.tests["my_boolean_custom_name_2"] == boolean_4
assert app.jinja_env.tests["my_boolean_custom_name_2"](False)
def test_add_template_test(app):
def boolean(value):
@ -320,6 +368,39 @@ def test_add_template_global(app, app_ctx):
rv = flask.render_template_string("{{ get_stuff() }}")
assert rv == "42"
@app.template_global
def get_stuff_1():
return "get_stuff_1"
assert "get_stuff_1" in app.jinja_env.globals.keys()
assert app.jinja_env.globals["get_stuff_1"] == get_stuff_1
assert app.jinja_env.globals["get_stuff_1"](), "get_stuff_1"
rv = flask.render_template_string("{{ get_stuff_1() }}")
assert rv == "get_stuff_1"
@app.template_global("my_get_stuff_custom_name_2")
def get_stuff_2():
return "get_stuff_2"
assert "my_get_stuff_custom_name_2" in app.jinja_env.globals.keys()
assert app.jinja_env.globals["my_get_stuff_custom_name_2"] == get_stuff_2
assert app.jinja_env.globals["my_get_stuff_custom_name_2"](), "get_stuff_2"
rv = flask.render_template_string("{{ my_get_stuff_custom_name_2() }}")
assert rv == "get_stuff_2"
@app.template_global(name="my_get_stuff_custom_name_3")
def get_stuff_3():
return "get_stuff_3"
assert "my_get_stuff_custom_name_3" in app.jinja_env.globals.keys()
assert app.jinja_env.globals["my_get_stuff_custom_name_3"] == get_stuff_3
assert app.jinja_env.globals["my_get_stuff_custom_name_3"](), "get_stuff_3"
rv = flask.render_template_string("{{ my_get_stuff_custom_name_3() }}")
assert rv == "get_stuff_3"
def test_custom_template_loader(client):
class MyFlask(flask.Flask):