Compare commits

...

11 Commits

Author SHA1 Message Date
d-lcw 4788bca78d
Merge 0abda4d277 into d6009c0aeb 2025-06-10 15:54:40 +01:00
David Lord d6009c0aeb
Merge branch 'stable'
pre-commit / main (push) Waiting to run Details
2025-06-09 21:20:46 -07:00
David Lord 2b42a803a2
cleanup svg
pre-commit / main (push) Waiting to run Details
2025-06-09 21:20:36 -07:00
David Lord 211cce038a
Merge branch 'stable'
pre-commit / main (push) Waiting to run Details
2025-06-09 14:33:44 -07:00
David Lord a7b67c99f9
svg logo (#5757)
pre-commit / main (push) Waiting to run Details
2025-06-09 14:33:26 -07:00
David Lord a758915893
svg logo 2025-06-09 14:31:31 -07:00
kadai0308 0abda4d277 docs: update docs and CHANGES.rst 2025-05-26 17:30:47 +08:00
pre-commit-ci-lite[bot] 45a0ff357f
[pre-commit.ci lite] apply automatic fixes 2025-05-26 09:26:20 +00:00
kadai0308 1f027e498d formatting 2025-05-26 17:23:06 +08:00
kadai0308 6d6c377a1f feat: support call template_test & template_global without parens 2025-05-26 17:20:12 +08:00
kadai0308 0a52b6b78d feat: support call template_filter without parens 2025-05-17 12:44:49 +08:00
15 changed files with 280 additions and 26 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

@ -1,3 +1,5 @@
<div align="center"><img src="https://raw.githubusercontent.com/pallets/flask/refs/heads/stable/docs/_static/flask-horizontal.svg" alt="" height="150"></div>
# Flask
Flask is a lightweight [WSGI] web application framework. It is designed

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

1
docs/_static/flask-horizontal.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

1
docs/_static/flask-icon.svg vendored Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 1000 1000" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g><path d="M448.955,119.915c-0.579,59.76 13.398,192.715 18.542,238.988l-356.626,159.589c-23.367,-39.985 -40.836,-96.035 -51.551,-140.858l-2.503,1.065c-8.573,3.718 -16.932,2.18 -20.609,-3.664l-0.456,-0.734l-34.175,-62.778c-2.402,-4.381 -2.054,-10.547 1.006,-16.32c3.06,-5.777 8.827,-11.157 14.901,-13.815l409.395,-175.316c6.074,-2.657 12.451,-2.59 16.655,0.176c4.44,2.587 5.304,5.387 5.421,13.667Z" style="fill:#3babc3;fill-rule:nonzero;"/><path d="M477.297,411.517l-343.415,153.677c20.185,38.246 45.103,78.851 75.476,118.011l302.647,-154.214c-6.532,-16.924 -12.929,-35.311 -34.708,-117.474Z" style="fill:#3babc3;fill-rule:nonzero;"/><path d="M243.67,724.046l289.522,-147.543c30.054,59.626 66.144,102.294 104.553,132.415c86.929,68.257 183.958,71.364 241.412,65.146l-1.966,-9.7c-0.612,-3.136 0.28,-5.597 3.584,-6.859l30.59,-11.989c5.211,-2.017 10.611,-0.495 14.279,3.584l31.177,-12.015c4.981,-2.008 10.611,-0.494 14.233,3.564l24.268,37.075c11.806,16.723 -1.924,27.342 -6.368,29.035l-39.431,15.407c1.542,5.232 1.471,13.31 -9.022,17.288l-30.636,11.969c-9.584,3.739 -16.61,-2.411 -17.524,-8.292l-0.998,-5.224c-184.485,74.282 -330.818,70.194 -445.692,26.944c-82.561,-31.081 -149.05,-82.639 -201.981,-140.805Z" style="fill:#3babc3;fill-rule:nonzero;"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

1
docs/_static/flask-vertical.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -58,8 +58,8 @@ html_sidebars = {
}
singlehtml_sidebars = {"index": ["project.html", "localtoc.html", "ethicalads.html"]}
html_static_path = ["_static"]
html_favicon = "_static/shortcut-icon.png"
html_logo = "_static/flask-vertical.png"
html_favicon = "_static/flask-icon.svg"
html_logo = "_static/flask-vertical.svg"
html_title = f"Flask Documentation ({version})"
html_show_sourcelink = False

View File

@ -3,8 +3,9 @@
Welcome to Flask
================
.. image:: _static/flask-horizontal.png
.. image:: _static/flask-horizontal.svg
:align: center
:height: 200px
Welcome to Flask's documentation. Flask is a lightweight WSGI web application framework.
It is designed to make getting started quick and easy, with the ability to scale up to

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):