Skip to content

Commit 6d6c377

Browse files
committed
feat: support call template_test & template_global without parens
1 parent 0a52b6b commit 6d6c377

File tree

4 files changed

+231
-25
lines changed

4 files changed

+231
-25
lines changed

src/flask/sansio/app.py

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -712,27 +712,47 @@ def add_template_filter(
712712

713713
@setupmethod
714714
def template_test(
715-
self, name: str | None = None
716-
) -> t.Callable[[T_template_test], T_template_test]:
715+
self, name: t.Callable[..., t.Any] | str | None = None
716+
) -> t.Callable[[T_template_test], T_template_test] | T_template_filter:
717717
"""A decorator that is used to register custom template test.
718718
You can specify a name for the test, otherwise the function
719719
name will be used. Example::
720720
721-
@app.template_test()
722-
def is_prime(n):
723-
if n == 2:
724-
return True
725-
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
726-
if n % i == 0:
727-
return False
721+
@app.template_test()
722+
def is_prime(n):
723+
if n == 2:
724+
return True
725+
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
726+
if n % i == 0:
727+
return False
728728
return True
729729
730+
The decorator also can be used without parentheses::
731+
732+
@app.template_test
733+
def is_prime(n):
734+
if n == 2:
735+
return True
736+
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
737+
if n % i == 0:
738+
return False
739+
730740
.. versionadded:: 0.10
731741
732742
:param name: the optional name of the test, otherwise the
733743
function name will be used.
734744
"""
735745

746+
if callable(name):
747+
# If name is callable, it is the function to register.
748+
# This is a shortcut for the common case of
749+
# @app.template_test
750+
# def func():
751+
752+
func = name
753+
self.add_template_test(func)
754+
return func
755+
736756
def decorator(f: T_template_test) -> T_template_test:
737757
self.add_template_test(f, name=name)
738758
return f
@@ -755,8 +775,8 @@ def add_template_test(
755775

756776
@setupmethod
757777
def template_global(
758-
self, name: str | None = None
759-
) -> t.Callable[[T_template_global], T_template_global]:
778+
self, name: t.Callable[..., t.Any] | str | None = None
779+
) -> t.Callable[[T_template_global], T_template_global] | T_template_filter:
760780
"""A decorator that is used to register a custom template global function.
761781
You can specify a name for the global function, otherwise the function
762782
name will be used. Example::
@@ -765,12 +785,28 @@ def template_global(
765785
def double(n):
766786
return 2 * n
767787
788+
The decorator also can be used without parentheses::
789+
790+
@app.template_global
791+
def double(n):
792+
return 2 * n
793+
768794
.. versionadded:: 0.10
769795
770796
:param name: the optional name of the global function, otherwise the
771797
function name will be used.
772798
"""
773799

800+
if callable(name):
801+
# If name is callable, it is the function to register.
802+
# This is a shortcut for the common case of
803+
# @app.template_global
804+
# def func():
805+
806+
func = name
807+
self.add_template_global(func)
808+
return func
809+
774810
def decorator(f: T_template_global) -> T_template_global:
775811
self.add_template_global(f, name=name)
776812
return f

src/flask/sansio/blueprints.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -442,15 +442,26 @@ def add_url_rule(
442442

443443
@setupmethod
444444
def app_template_filter(
445-
self, name: str | None = None
446-
) -> t.Callable[[T_template_filter], T_template_filter]:
445+
self, name: t.Callable[..., t.Any] | str | None = None
446+
) -> t.Callable[[T_template_filter], T_template_filter] | T_template_filter:
447447
"""Register a template filter, available in any template rendered by the
448448
application. Equivalent to :meth:`.Flask.template_filter`.
449449
450450
:param name: the optional name of the filter, otherwise the
451451
function name will be used.
452452
"""
453453

454+
if callable(name):
455+
# If name is callable, it is the function to register.
456+
# This is a shortcut for the common case of
457+
# @bp.add_template_filter
458+
# def func():
459+
460+
func = name
461+
self.add_app_template_filter(func)
462+
return func
463+
464+
454465
def decorator(f: T_template_filter) -> T_template_filter:
455466
self.add_app_template_filter(f, name=name)
456467
return f
@@ -476,8 +487,8 @@ def register_template(state: BlueprintSetupState) -> None:
476487

477488
@setupmethod
478489
def app_template_test(
479-
self, name: str | None = None
480-
) -> t.Callable[[T_template_test], T_template_test]:
490+
self, name: t.Callable[..., t.Any] | str | None = None
491+
) -> t.Callable[[T_template_test], T_template_test] | T_template_filter:
481492
"""Register a template test, available in any template rendered by the
482493
application. Equivalent to :meth:`.Flask.template_test`.
483494
@@ -487,6 +498,16 @@ def app_template_test(
487498
function name will be used.
488499
"""
489500

501+
if callable(name):
502+
# If name is callable, it is the function to register.
503+
# This is a shortcut for the common case of
504+
# @bp.add_template_test
505+
# def func():
506+
507+
func = name
508+
self.add_app_template_test(func)
509+
return func
510+
490511
def decorator(f: T_template_test) -> T_template_test:
491512
self.add_app_template_test(f, name=name)
492513
return f
@@ -514,8 +535,8 @@ def register_template(state: BlueprintSetupState) -> None:
514535

515536
@setupmethod
516537
def app_template_global(
517-
self, name: str | None = None
518-
) -> t.Callable[[T_template_global], T_template_global]:
538+
self, name: t.Callable[..., t.Any] | str | None = None
539+
) -> t.Callable[[T_template_global], T_template_global] | T_template_filter:
519540
"""Register a template global, available in any template rendered by the
520541
application. Equivalent to :meth:`.Flask.template_global`.
521542
@@ -524,6 +545,15 @@ def app_template_global(
524545
:param name: the optional name of the global, otherwise the
525546
function name will be used.
526547
"""
548+
if callable(name):
549+
# If name is callable, it is the function to register.
550+
# This is a shortcut for the common case of
551+
# @bp.add_template_global
552+
# def func():
553+
554+
func = name
555+
self.add_app_template_global(func)
556+
return func
527557

528558
def decorator(f: T_template_global) -> T_template_global:
529559
self.add_app_template_global(f, name=name)

tests/test_blueprints.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,11 +366,37 @@ def test_template_filter(app):
366366
def my_reverse(s):
367367
return s[::-1]
368368

369+
@bp.app_template_filter
370+
def my_reverse_2(s):
371+
return s[::-1]
372+
373+
@bp.app_template_filter("my_reverse_custom_name_3")
374+
def my_reverse_3(s):
375+
return s[::-1]
376+
377+
@bp.app_template_filter(name="my_reverse_custom_name_4")
378+
def my_reverse_4(s):
379+
return s[::-1]
380+
369381
app.register_blueprint(bp, url_prefix="/py")
370382
assert "my_reverse" in app.jinja_env.filters.keys()
371383
assert app.jinja_env.filters["my_reverse"] == my_reverse
372384
assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba"
373385

386+
assert "my_reverse_2" in app.jinja_env.filters.keys()
387+
assert app.jinja_env.filters["my_reverse_2"] == my_reverse_2
388+
assert app.jinja_env.filters["my_reverse_2"]("abcd") == "dcba"
389+
390+
391+
assert "my_reverse_custom_name_3" in app.jinja_env.filters.keys()
392+
assert app.jinja_env.filters["my_reverse_custom_name_3"] == my_reverse_3
393+
assert app.jinja_env.filters["my_reverse_custom_name_3"]("abcd") == "dcba"
394+
395+
396+
assert "my_reverse_custom_name_4" in app.jinja_env.filters.keys()
397+
assert app.jinja_env.filters["my_reverse_custom_name_4"] == my_reverse_4
398+
assert app.jinja_env.filters["my_reverse_custom_name_4"]("abcd") == "dcba"
399+
374400

375401
def test_add_template_filter(app):
376402
bp = flask.Blueprint("bp", __name__)
@@ -502,11 +528,35 @@ def test_template_test(app):
502528
def is_boolean(value):
503529
return isinstance(value, bool)
504530

531+
@bp.app_template_test
532+
def boolean_2(value):
533+
return isinstance(value, bool)
534+
535+
@bp.app_template_test("my_boolean_custom_name")
536+
def boolean_3(value):
537+
return isinstance(value, bool)
538+
539+
@bp.app_template_test(name="my_boolean_custom_name_2")
540+
def boolean_4(value):
541+
return isinstance(value, bool)
542+
505543
app.register_blueprint(bp, url_prefix="/py")
506544
assert "is_boolean" in app.jinja_env.tests.keys()
507545
assert app.jinja_env.tests["is_boolean"] == is_boolean
508546
assert app.jinja_env.tests["is_boolean"](False)
509547

548+
assert "boolean_2" in app.jinja_env.tests.keys()
549+
assert app.jinja_env.tests["boolean_2"] == boolean_2
550+
assert app.jinja_env.tests["boolean_2"](False)
551+
552+
assert "my_boolean_custom_name" in app.jinja_env.tests.keys()
553+
assert app.jinja_env.tests["my_boolean_custom_name"] == boolean_3
554+
assert app.jinja_env.tests["my_boolean_custom_name"](False)
555+
556+
assert "my_boolean_custom_name_2" in app.jinja_env.tests.keys()
557+
assert app.jinja_env.tests["my_boolean_custom_name_2"] == boolean_4
558+
assert app.jinja_env.tests["my_boolean_custom_name_2"](False)
559+
510560

511561
def test_add_template_test(app):
512562
bp = flask.Blueprint("bp", __name__)
@@ -679,6 +729,18 @@ def test_template_global(app):
679729
def get_answer():
680730
return 42
681731

732+
@bp.app_template_global
733+
def get_stuff_1():
734+
return "get_stuff_1"
735+
736+
@bp.app_template_global("my_get_stuff_custom_name_2")
737+
def get_stuff_2():
738+
return "get_stuff_2"
739+
740+
@bp.app_template_global(name="my_get_stuff_custom_name_3")
741+
def get_stuff_3():
742+
return "get_stuff_3"
743+
682744
# Make sure the function is not in the jinja_env already
683745
assert "get_answer" not in app.jinja_env.globals.keys()
684746
app.register_blueprint(bp)
@@ -688,10 +750,31 @@ def get_answer():
688750
assert app.jinja_env.globals["get_answer"] is get_answer
689751
assert app.jinja_env.globals["get_answer"]() == 42
690752

753+
assert "get_stuff_1" in app.jinja_env.globals.keys()
754+
assert app.jinja_env.globals["get_stuff_1"] == get_stuff_1
755+
assert app.jinja_env.globals["get_stuff_1"](), "get_stuff_1"
756+
757+
assert "my_get_stuff_custom_name_2" in app.jinja_env.globals.keys()
758+
assert app.jinja_env.globals["my_get_stuff_custom_name_2"] == get_stuff_2
759+
assert app.jinja_env.globals["my_get_stuff_custom_name_2"](), "get_stuff_2"
760+
761+
assert "my_get_stuff_custom_name_3" in app.jinja_env.globals.keys()
762+
assert app.jinja_env.globals["my_get_stuff_custom_name_3"] == get_stuff_3
763+
assert app.jinja_env.globals["my_get_stuff_custom_name_3"](), "get_stuff_3"
764+
691765
with app.app_context():
692766
rv = flask.render_template_string("{{ get_answer() }}")
693767
assert rv == "42"
694768

769+
rv = flask.render_template_string("{{ get_stuff_1() }}")
770+
assert rv == "get_stuff_1"
771+
772+
rv = flask.render_template_string("{{ my_get_stuff_custom_name_2() }}")
773+
assert rv == "get_stuff_2"
774+
775+
rv = flask.render_template_string("{{ my_get_stuff_custom_name_3() }}")
776+
assert rv == "get_stuff_3"
777+
695778

696779
def test_request_processing(app, client):
697780
bp = flask.Blueprint("bp", __name__)

0 commit comments

Comments
 (0)