mako icon indicating copy to clipboard operation
mako copied to clipboard

Template.render_context(context) (re)sets context['self']

Open sqlalchemy-bot opened this issue 12 years ago • 8 comments

Migrated issue, originally created by Anonymous

I'm not quite positive that this is a bug, but I’m pretty sure it is.

When Template.render_context() is called, it sets context['self']. So, if one renders another template like this:

subtemplate.render_context(context)

After the call to render_context(), context['self'] no longer contains the correct namespace. This (at least) causes problems for any <%block>s in the calling template.

I've attached a short test script which demonstrates the problem.


Attachments: test_render_context_bug.py

sqlalchemy-bot avatar Feb 25 '12 14:02 sqlalchemy-bot

Anonymous wrote:

Not sure, but I think the fix might be to call context._clean_inheritance_tokens() (to a get a clean ''copy'' of the context) in mako.runtime._render_context().

sqlalchemy-bot avatar Feb 25 '12 15:02 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

well it's not a quite a "bug" since I never intended people to be calling "render_context()" from within templates, but I guess recently some folks have really wanted the feature of templates being called as an entirely independent entity. Really, there just needs to be some library call for this, so in this case I'd rather give you a workaround:

    subtmpl.render_context(context._copy())

sqlalchemy-bot avatar Feb 25 '12 15:02 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

also can I get a clear picture of the use case for include + full context so that we can figure out how this feature should work?

sqlalchemy-bot avatar Feb 25 '12 15:02 sqlalchemy-bot

Anonymous wrote:

Replying to [comment:2 zzzeek]:

well it's not a quite a "bug" since I never intended people to be calling "render_context()" from within templates, but I guess recently some folks have really wanted the feature of templates being called as an entirely independent entity.

Fair enough. (It might just be "some folk" --- Jeff Dairiki here.)

Out of curiosity, what is the intended use case for Template.render_context() vs. say, plain old .render() or .render_unicode()?


Replying to [comment:3 zzzeek]:

also can I get a clear picture of the use case for include + full context so that we can figure out how this feature should work?

I'm trying to develop a pluggable "theming" API for a web app.
There are data types, say FancyTable, that ''themes'' know how to render. So theme.render(some_fancy_table) wants to return something which represents some (possibly large amount of) HTML text. It could just return a string, but — in the name of premature optimization — I'd like to allow it to return something with a .render_context() method (which might be implemented as a mako Template.)

Mostly I'm interested just in access to context.write(), but, it does turn out to be convenient to have access to other "renderer globals" via the context as well.

Hrmmph. That may not have clarified things much — sorry. Mostly, I think, I just want a version of Template.render_context() which is safe to call from within another template. But you already knew that.

sqlalchemy-bot avatar Feb 25 '12 17:02 sqlalchemy-bot

Anonymous wrote:

Replying to [comment:2 zzzeek]:

... so in this case I'd rather give you a workaround:

subtmpl.render_context(context._copy())

I think this should be

    subtmpl.render_context(context._clean_inheritance_tokens())

so that subtmpl doesn't get the callers next and parent.


Perhaps the solution is to change Template.render_context() so that it does a

    context = context._clean_inheritance_tokens()

but only in the cases where that is necessary. "Where that is necessary" is perhaps when context._with_template is already set (or maybe when 'self' in context.)

(I don't really understand the purpose of [source:mako/template.py@536#L398 this check] for context._with_template if it is not expecting to be called from within another template, so most likely I am missing something...)

sqlalchemy-bot avatar Feb 26 '12 17:02 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

well I wouldn't say there was intent for some specific use case with that check for _with_template other than, the check is there because it should only be set once. Interestingly, there's only one place Mako seems to call template.render_context() itself and that's with the error template render, and it resets _with_template there. So yeah it starts to look like render_context() really means to be called with a context that's "clean" of artifacts from a different parent template.

But render_context() has been the way it is for five years now so it's a little scary to change it.

Went back to your original email. You said the problem with include is that you can't say <%include file="derived.mako" args="x=${x}"/>. Why not ? Here's an example:

from mako.template import Template
from mako.lookup import TemplateLookup

lookup = TemplateLookup()
lookup.put_string("base", 
"""
this is base

${next.body(**pageargs)}

"""
)

lookup.put_string("subtemp",
"""
<%page args="x"/>
hi ${x}
"""
)

lookup.put_string("index", """
    <%
        x = 5
    %>
    <%include file="subtemp" args="x='5'"/>
""")


print lookup.get_template("index").render()

what's the part that's not working ?

sqlalchemy-bot avatar Feb 27 '12 12:02 sqlalchemy-bot

Anonymous wrote:

Replying to [comment:6 zzzeek]:

well I wouldn't say there was intent for some specific use case with that check for _with_template other than, the check is there because it should only be set once. Interestingly, there's only one place Mako seems to call template.render_context() itself and that's with the error template render, and it resets _with_template there. So yeah it starts to look like render_context() really means to be called with a context that's "clean" of artifacts from a different parent template.

But render_context() has been the way it is for five years now so it's a little scary to change it.

It seems like changing render_context so that it calls _clean_inheritance_tokens really only does two things which could cause trouble:

  1. It copies the context. The only potential for surprise from that (other than efficiency loss due to a possibly unnecessary copy) seems minimal. It would only cause problems if the called template is mucking in the callers context in ways that are explicitly warned against [http://docs.makotemplates.org/en/latest/runtime.html#context-variables in the docs] (ref: the paragraph beginning "Another facet of the Context is that its dictionary of variables is immutable.")

  2. It unsets parent and next if they were set in the original context. Again, counting on passing these to a template seems like a bad idea. They should arise only from the template inheritance structure (as declared by <%inherit> tags), right?

On the flip side, ''not'' copying the context seems to have the ability to create quite a bit of surprise, e.g. the mangling of self in the calling templates context that I experienced. The patching of the called template into the calling templates inheritance chain seems likely to cause unexpectedness, as well.


Went back to your original email. You said the problem with include is that you can't say <%include file="derived.mako" args="x=${x}"/>.

(This is somewhat unrelated to this ticket. ... And no longer an issue for me...)

Why not ? Here's an example:

[...] <% x = 5 %> <%include file="subtemp" args="x='5'"/> [...]

what's the part that's not working ?

What doesn't work is if you change that <%include> to:

<%include file="subtemp" args="x=${x}"/>

sqlalchemy-bot avatar Feb 27 '12 21:02 sqlalchemy-bot

Michael Bayer (@zzzeek) wrote:

OK the "args" element is already parsed as a Python argument list, seems to work if you say:

    <%
        x = 5
    %>
    <%include file="subtemp" args="x=x"/>

sqlalchemy-bot avatar Mar 12 '12 14:03 sqlalchemy-bot