mako
mako copied to clipboard
Template.render_context(context) (re)sets context['self']
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
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().
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())
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?
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.
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...)
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 ?
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:
-
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.")
-
It unsets
parent
andnext
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}"/>
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"/>