Issues

Issue #184 new

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

guest
created an issue

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.

Comments (8)

  1. guest reporter

    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().

  2. Mike Bayer repo owner

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

  3. guest reporter

    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.

  4. guest reporter

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

  5. Mike Bayer repo owner

    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:

    1. !python 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 ?

  6. guest reporter

    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}"/>
    
  7. Mike Bayer repo owner

    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"/>

  8. Log in to comment