mako icon indicating copy to clipboard operation
mako copied to clipboard

option to apply filters before the defaults ?

Open sqlalchemy-bot opened this issue 13 years ago • 14 comments

Migrated issue, originally created by Anonymous

Maybe I am wrong here, but shouldn't default filters such as unicode() be applied last? The fact that it's done the exactly opposite way makes it a pain, as we are limited to deal with the string/unicode representation of whatever variable is passed to the filter.

Hence, I propose the following change:

diff -r 29d9a098a70465450563e9c07ed613e227886936 mako/codegen.py
--- a/mako/codegen.py   Fri Aug 05 17:45:51 2011 -0400
+++ b/mako/codegen.py   Mon Aug 08 11:14:48 2011 +0200
@@ -669,7 +669,7 @@
                 if self.compiler.pagetag:
                     args = self.compiler.pagetag.filter_args.args + args
                 if self.compiler.default_filters:
-                    args = self.compiler.default_filters + args
+                    args = args + self.compiler.default_filters
         for e in args:
             # if filter given as a function, get just the identifier portion
             if e == 'n':

What do you think?

Cheers,

Pedro

sqlalchemy-bot avatar Aug 08 '11 05:08 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

Each of the u, h, and x filters expect a string. So if i said:

${x | h}

expecting "x" to be HTML encoded, then "x" happens to be an integer or some other non-string that is coerced by unicode(), kaboom.

The patch above breaks the one test we have in this regard which checks this:

    def test_convert_str(self):
        """test that string conversion happens in expressions before sending to filters"""
        t = Template("""
            ${x | trim}
        """)
        assert flatten_result(t.render(x=5)) == "5"

now I am absolutely aware what a PITA this is for non-string oriented filters like formatters. All throughout our templates, we just have a lot of "n" going on.

I have the impression that a better idea would be needed here. ${x | y} has "y" as a "filter" - it processes output. In practice, we use ${x | y} a lot for '''formatting'''. This is a different role. I'm seeing the "Filtering and Buffering" chapter being renamed "Filtering, Formatting, and Buffering" in the docs. Syntax change ?

${x | formatter1, formatter2, ... | filter1, filter2, ...}

${x & formatter1, formatter2, ... | filter1, filter2, ...}

?

then again, the usual approach:

${x | formatter1, n}

is actually more efficient than having the formatter + the unicode. The "formatters" we use already return a fully composed string, so the extra "unicode" call would be overhead (and all those unicode calls do add up). So, not sure I want to do anything here, perhaps update the docs to discuss the roles of "filter"/"formatting" regardless.

sqlalchemy-bot avatar Aug 08 '11 11:08 sqlalchemy-bot

Anonymous wrote:

An alternative could be placing letting the conversion to string be done by those filters, no? If h and trim are expecting strings, they should make sure all the arguments are properly converted to strings beforehand.

Pedro

sqlalchemy-bot avatar Aug 10 '11 09:08 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

Replying to [comment:2 guest]:

An alternative could be placing letting the conversion to string be done by those filters, no? If h and trim are expecting strings, they should make sure all the arguments are properly converted to strings beforehand.

but that is also wasteful and redundant, as well as an implicit behavior I don't like very much, for each filter to also embed string conversion within it. the filtering of unicode etc. is the primary performance bottleneck in mako.

sqlalchemy-bot avatar Jan 16 '12 13:01 sqlalchemy-bot

Anonymous wrote:

Replying to [comment:3 zzzeek]:

I see. Too bad then. Still, a solution that would be easier than changing the whole syntax could be finding a way of implying the n in some of the filters without having to specify it manually. Maybe something like this could work?

def json(obj):
    # ...
json.exclusive = True

I don't know about the performance penalty though.

sqlalchemy-bot avatar Jan 17 '12 03:01 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

having the formatter advertise as such might be nice, though again that's a runtime check since filters/formatters can be passed into the template.

A special character, perhaps:

${mydata | >json}

sqlalchemy-bot avatar Jan 17 '12 12:01 sqlalchemy-bot

Anonymous wrote:

([email protected] here — can I create a new account for Trac somehow so I can get followups by email?)

I wanted to add a json filter and hoped to make it usable as ${x | json} without having to add any extra n filters. I only realized that the default_filters are applied before other filters after messing around with this for a day.

Could the name be changed to initial_filters or something similar? That is, the name could be made clearer to indicate that the filters are not ''defaults'' (that can be overwritten later) but unconditionally applied filters.

Adding "formatters" would be nice — anything that lets us work on the Python values before they're turned into strings.

I'll now go and change my ${x | json} fragments into ${json(x)} in the templates. That '''does''' work since it is compiled into what I need: filters.html_escape(json(x)). My filter outputs a unicode subclass which defines __html__ to return self, which makes the html_escape a no-op.

sqlalchemy-bot avatar Aug 28 '13 05:08 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

my plan is entirely to move this trac to the bitbucket repo, but I don't have a completed script that can convert all the issues to their import format (I have one I was writing, but it's a big project).

Feel free to just discuss on the mailing list, this trac is pretty out of the loop.

sqlalchemy-bot avatar Aug 28 '13 10:08 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

oh but name change, i dont really think we can change any names. mako is installed very broadly in projects that don't really want to look at their mako code very often so i dont think backwards incompatible anything is possible right now.

sqlalchemy-bot avatar Aug 28 '13 10:08 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

if "default_filters" is ambiguous, i dont see why we can't just make sure the documentation makes these clear. in my mind "default" means, "the filters that get applied in all cases no matter what".

where would "formatters" go exactly, im not understanding that ? the "turning things into strings" is what default_filters does right now, if you put a function of your own in there, it replaces the "turning things into strings" with whatever you want. the proposal here is to somehow extend the "|" syntax to allow filters before or after the default chain.

sqlalchemy-bot avatar Aug 28 '13 10:08 sqlalchemy-bot

Anonymous wrote:

(Martin here). I'm not a native English speaker, so my understanding of "default" may be wrong. I had just expected it to be the filters that would be applied ''unless'' an expression specified other filters. Like default arguments for Python: def foo(x=10) defines a function where x is 10 ''unless'' I specify otherwise when I call the function.

It doesn't matter much, though, and I agree that updating the documentation would be the way to go here. You should definitely not make backwards incompatible changes ("renaming" the keyword argument could be done by adding a new one and only documenting that but still accepting the old one).

With regard to formatters or filters: when I first read about Mako filters, I thought that ${x | foo} was equivalent to ${foo(x)}. This was further reinforced when I saw that you can define a function inside a template and use that as a filter. The crucial difference, though, is the order of application and that wasn't clear to me until I read create_filter_callable in codegen.py.

sqlalchemy-bot avatar Aug 29 '13 02:08 sqlalchemy-bot

Anonymous wrote:

PS: Thanks for the hint about the mailinglist, I'll use that next time!

sqlalchemy-bot avatar Aug 29 '13 03:08 sqlalchemy-bot

Changes by Michael Bayer (@zzzeek):

  • changed title from "default filters should be applied last" to "option to apply filters before the defaults ?"

sqlalchemy-bot avatar Mar 21 '13 11:03 sqlalchemy-bot

How about adding two more settings, fallback_filters and final_filters, in addition to default_filters?

You could have fallback_filters be used if there are no other filters specified. This captures the understanding of default voided in this request here. It also allows for fallback_filters to perform some escaping or formatting that is right most of the time, and to specify an alternate formatting or escaping in situations where it is not.

And you'd have final_filters applied to the result. I would suggest doing so unconditionally. That would address #272 which could use final_filters=['str'] to achieve conversion of everything to strings. It would also allow use as a safety feature: some form of safe HTML library could force-escape its inputs unless they are objects from that library which have been constructed in some other fashion that ensures proper escaping.

gagern avatar Jul 02 '19 17:07 gagern

Just re-read all this.

my first thought is that whatever we do here should fix #171 and #272 at the same time as these seem to be asking for almost the same thing.

my next thought is, if we have: fallback_filters (if no filters) -> default filters (if no n) -> template filters from "|" -> final_filters, that looks like a rule chain to me, which the proposal here is to hardcode.

can we instead have an actual filter rule chain and just build up a default that looks like the above, but folks can go in there and build whatever chain they want and/or we can document certain kinds of chains etc.?

I'm not sure how these chains could be best specified. Let's say they want to run "u" and "trim" if no other template filters are given, "sanitize" if no "n" is given, then the template specified filters, then a final filter called "stringify". Three new "meta" filters fallback(), default(), and template() would be control flow containers. A specification might look like:

from mako import filters
TemplateLookup(filter_chain=[filters.fallback(filters.u ,filters.trim), filters.default(filters.sanitize), filters.template(), filters.stringify)

I'm not sure about "filters.u" vs "u" but the main thing is, instead of having three keyword arguments that refer to a hardcoded flow that would need to be documented, have an open-ended rule chain that is largely self documenting and can be changed and extended.

zzzeek avatar Jul 02 '19 21:07 zzzeek