flask icon indicating copy to clipboard operation
flask copied to clipboard

The `template_filter` decorator doesn't work if you don't pass an argument

Open alexwlchan opened this issue 6 months ago • 8 comments
trafficstars

What's the issue?

You can use template_filter as a decorator, but it only registers the filter if you write an explicit name or an empty set of parentheses. If you call it without parens, the filter doesn't get registered.

It's a small difference and can be confusing.

Minimal example

Consider the following program:

from flask import Flask, render_template_string


app = Flask(__name__)


@app.template_filter
def double(x):
    return x * 2


@app.route("/")
def index():
    return render_template_string("2 times 2 is {{ 2 | double }}")

If you run this app (flask run --port 8008 --debug) and then open it in your browser (http://localhost:8008) you'll get an error:

jinja2.exceptions.TemplateAssertionError: No filter named 'double'.

This is confusing, and it took me a while to realise the missing parentheses in app.template_filter were at fault.

Suggested fix

I think it would be helpful if the decorator either:

  • Supported being called without parentheses, or
  • Printed an explicit warning if called this way, e.g. Did you use 'template_filter' as a decorator without parentheses? You need to call it with 'template_filter()'

This is caught by type checkers, but not everybody type checks their Python and the error message is less obvious:

Argument 1 to "template_filter" of "App" has incompatible type "Callable[[Any], Any]"; expected "str | None"

I've had a look at the relevant code, and I'd be happy to provide a patch if you think this is a useful change.

Environment

  • Python version: Python 3.11.11
  • Flask version: Flask 3.1.0

alexwlchan avatar May 12 '25 08:05 alexwlchan

@davidism Hi! 👋 I'm new to contributing to Flask and really interested in getting involved in the project. This issue looks like something I could help with and learn from — would it be okay if I took it on?

I'm not entirely sure whether this suggestion aligns with Flask’s design philosophy and consistency with similar decorators. If it's confirmed that this is a change the team would consider, I’d love to help implement it.

Could I be assigned as the assignee for this issue?

Thanks!

kadai0308 avatar May 14 '25 16:05 kadai0308

@alexwlchan is it okay if i take up this issue? I understand from https://palletsprojects.com/contributing that I don't have to ask first but I thought it would be a good idea to do so out of courtesy anyway, if you could assign me. Thanks!

Gable-github avatar May 16 '25 17:05 Gable-github

Not the author of the issue (not sure why you pinged him) but a maintainer: Yes sure, feel free to work on it. PRs to fix bugs are always welcome!

ThiefMaster avatar May 16 '25 17:05 ThiefMaster

Ok, I will just try to work on PR.

kadai0308 avatar May 17 '25 03:05 kadai0308

Hi there!

I'm keen to start contributing to Flask and this issue caught my eye. It seems like a great way for me to get familiar with the project. If addressing this decorator behavior fits the team's vision for Flask's design, I'd be happy to take it on. Could I get assigned?

Thanks!

lekuG12 avatar May 22 '25 14:05 lekuG12

There's already a draft PR for this... why do you want to start working on yet another one while there's an open PR? :)

ThiefMaster avatar May 22 '25 14:05 ThiefMaster

There's already a draft PR for this... why do you want to start working on yet another one while there's an open PR? :)

Thanks for letting me know... I appreciate the heads-up about the existing draft PR. I'll definitely find another issue to work on.

lekuG12 avatar May 22 '25 14:05 lekuG12

What is it about this that has attracted five completely new contributors who aren't reading the contributing guide:

Anyone is welcome to work on any open ticket in any project's issue tracker, without asking first. Before starting, check if anyone else is assigned to the issue, or if there are any linked open pull requests. Look through the issue for that information as well as discussion and other linked issues for context.

davidism avatar May 22 '25 14:05 davidism

I did a survey of all the decorator methods to see how we were using the different styles: with parentheses (decorator factory), without parentheses, and non-decorator methods.

No parentheses, fail if called without argument.

  • before_request(f)
  • after_reqeust(f)
  • teardown_request(f)
  • teardown_appcontext(f)
  • url_value_preprocessor(f)
  • url_defaults(f)
  • context_processor(f)
  • shell_context_processor(f)

Routing related, none make sense without parentheses, fail if called without argument. None of these fail if used as a decorator without parentheses.

  • get(str)(f)
  • post(str)(f)
  • put(str)(f)
  • delete(str)(f)
  • patch(str)(f)
  • route(str)(f)
    • add_url_rule(str, f)
  • endpoint(str)(f) no corresponding add_endpoint(str, f), uncommon
  • errorhandler(int)(f)
    • register_error_handler(int, f)

CLI provides with and without parentheses styles, as well as non-decorator version. Name is inferred from function if not given, for all styles.

  • cli.command(str?)(f)
    • cli.command(f)
    • cli.add_command(f, str?)

Template related, only provides with parentheses style, as well as non-decorator version. Name is inferred from function if not given, for all styles.

  • template_filter(str?)(f)
    • add_template_filter(f, str?)
  • template_test(str?)(f)
    • add_template_test(f, str?)
  • template_global(str?)(f)
    • add_template_global(f, str?)

Incorrect uses for all of these are caught by type checkers. At runtime, all the decorator factories, not only the template related ones, do not fail if used without parentheses. They will create a decorator function that would use invalid arguments, but that function never gets called so nothing happens. In general, Python code does not do runtime type checking of arguments. Static type checkers now exist to fill that role without affecting runtime performance.

Every decorator factory (except the uncommon endpoint) has a corresponding non-decorator method. I don't think I've ever seen an issue where someone did @app.route instead of @app.route("/"). What makes the template decorators special? We could look to the CLI to justify that. Click recently added the ability to use the command decorator with or without parentheses, since the name was always inferred from the function if not given. However, this makes the typing much more complicated (more so in Click's case than these decorators in Flask).

I don't think we want to raise an error, unless we also want to do that in the route decorators, and I don't think there's a need to there. The question is if we keep Click as a special case, or extend the idea of "if all arguments are optional, allow both with and without parentheses styles".

davidism avatar Aug 19 '25 16:08 davidism