aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGES103
-rw-r--r--examples/bench/basic.py2
-rw-r--r--lib/mako/codegen.py36
-rw-r--r--lib/mako/runtime.py54
-rw-r--r--test/call.py7
-rw-r--r--test/filters.py1
6 files changed, 141 insertions, 62 deletions
diff --git a/CHANGES b/CHANGES
index 1e830ef..7e5c11c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,36 +1,75 @@
0.2.0
-- added "attr" accessor to namespaces. Returns attributes
- configured as module level attributes, i.e. within
- <%! %> sections [ticket:62]
-- fixed bug in python generation when variable names are used
- with identifiers like "else", "finally", etc. inside them
- [ticket:68]
-- fixed codegen bug which occured when using <%page> level
- caching, combined with an expression-based cache_key,
- combined with the usage of <%namespace import="*"/>
-- fixed lexer exceptions not cleaning up temporary files, which
- could lead to a maximum number of file descriptors used in the
- process [ticket:69]
-- fixed issue with inline format_exceptions that was producing
- blank exception pages when an inheriting template is
- present [ticket:71]
-- format_exceptions will apply the encoding options
- of html_error_template() to the buffered output
-- rewrote the "whitespace adjuster" function to work with
- more elaborate combinations of quotes and comments
- [ticket:75]
-- cache_key argument can now render arguments passed
- directly to the %page or %def, i.e.
- <%def name="foo(x)" cached="True" cache_key="${x}"/>
- [ticket:78]
-- added "bytestring passthru" mode, via `disable_unicode=True`
- argument passed to Template or TemplateLookup. All
- unicode-awareness and filtering is turned off, and template
- modules are generated with the appropriate magic encoding
- comment. In this mode, template expressions can only
- receive raw bytestrings or Unicode objects which represent
- straight ASCII, and render_unicode() may not be used.
- [ticket:77] (courtesy anonymous guest)
+- Speed improvements (as though we needed them, but people
+ contributed and there you go):
+
+ - added "bytestring passthru" mode, via
+ `disable_unicode=True` argument passed to Template or
+ TemplateLookup. All unicode-awareness and filtering is
+ turned off, and template modules are generated with
+ the appropriate magic encoding comment. In this mode,
+ template expressions can only receive raw bytestrings
+ or Unicode objects which represent straight ASCII, and
+ render_unicode() may not be used if multibyte
+ characters are present. When enabled, speed
+ improvement around 10-20%. [ticket:77] (courtesy
+ anonymous guest)
+
+ - inlined the "write" function of Context into a local
+ template variable. This affords a 12-30% speedup in
+ template render time. (idea courtesy same anonymous
+ guest) [ticket:76]
+
+- New Features, API changes:
+
+ - added "attr" accessor to namespaces. Returns
+ attributes configured as module level attributes, i.e.
+ within <%! %> sections. [ticket:62] i.e.:
+
+ # somefile.html
+ <%!
+ foo = 27
+ %>
+
+ # some other template
+ <%namespace name="myns" file="somefile.html"/>
+ ${myns.attr.foo}
+
+ The slight backwards incompatibility here is, you
+ can't have namespace defs named "attr" since the
+ "attr" descriptor will occlude it.
+
+ - cache_key argument can now render arguments passed
+ directly to the %page or %def, i.e. <%def
+ name="foo(x)" cached="True" cache_key="${x}"/>
+ [ticket:78]
+
+ - some functions on Context are now private:
+ _push_buffer(), _pop_buffer(),
+ caller_stack._push_frame(), caller_stack._pop_frame().
+
+- Bugfixes:
+
+ - fixed bug in python generation when variable names are
+ used with identifiers like "else", "finally", etc.
+ inside them [ticket:68]
+
+ - fixed codegen bug which occured when using <%page>
+ level caching, combined with an expression-based
+ cache_key, combined with the usage of <%namespace
+ import="*"/> - fixed lexer exceptions not cleaning up
+ temporary files, which could lead to a maximum number
+ of file descriptors used in the process [ticket:69]
+
+ - fixed issue with inline format_exceptions that was
+ producing blank exception pages when an inheriting
+ template is present [ticket:71]
+
+ - format_exceptions will apply the encoding options of
+ html_error_template() to the buffered output
+
+ - rewrote the "whitespace adjuster" function to work
+ with more elaborate combinations of quotes and
+ comments [ticket:75]
0.1.10
- fixed propagation of 'caller' such that nested %def calls
diff --git a/examples/bench/basic.py b/examples/bench/basic.py
index c603a26..84c4b11 100644
--- a/examples/bench/basic.py
+++ b/examples/bench/basic.py
@@ -65,7 +65,7 @@ def myghty(dirname, verbose=False):
def mako(dirname, verbose=False):
from mako.template import Template
from mako.lookup import TemplateLookup
- lookup = TemplateLookup(directories=[dirname], filesystem_checks=False)
+ lookup = TemplateLookup(directories=[dirname], filesystem_checks=False, disable_unicode=True)
template = lookup.get_template('template.html')
def render():
return template.render(title="Just a test", user="joe", list_items=[u'Number %d' % num for num in range(1,15)])
diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py
index d59e487..e6b5476 100644
--- a/lib/mako/codegen.py
+++ b/lib/mako/codegen.py
@@ -163,11 +163,11 @@ class _GenerateRenderMethod(object):
this could be the main render() method or that of a top-level def."""
self.printer.writelines(
"def %s(%s):" % (name, ','.join(args)),
- "context.caller_stack.push_frame()",
+ "context.caller_stack._push_frame()",
"try:"
)
if buffered or filtered or cached:
- self.printer.writeline("context.push_buffer()")
+ self.printer.writeline("context._push_buffer()")
self.identifier_stack.append(self.compiler.identifiers.branch(self.node))
if not self.in_def and '**pageargs' in args:
@@ -302,6 +302,8 @@ class _GenerateRenderMethod(object):
else:
self.printer.writeline("%s = context.get(%s, UNDEFINED)" % (ident, repr(ident)))
+ self.printer.writeline("__M_writer = context.writer()")
+
def write_source_comment(self, node):
"""write a source comment containing the line number of the corresponding template line."""
if self.last_source_line != node.lineno:
@@ -329,12 +331,12 @@ class _GenerateRenderMethod(object):
buffered = eval(node.attributes.get('buffered', 'False'))
cached = eval(node.attributes.get('cached', 'False'))
self.printer.writelines(
- "context.caller_stack.push_frame()",
+ "context.caller_stack._push_frame()",
"try:"
)
if buffered or filtered or cached:
self.printer.writelines(
- "context.push_buffer()",
+ "context._push_buffer()",
)
identifiers = identifiers.branch(node, nested=nested)
@@ -362,16 +364,16 @@ class _GenerateRenderMethod(object):
if callstack:
self.printer.writelines(
"finally:",
- "context.caller_stack.pop_frame()",
+ "context.caller_stack._pop_frame()",
None
)
if buffered or filtered or cached:
self.printer.writelines(
"finally:",
- "__M_buf = context.pop_buffer()"
+ "__M_buf, __M_writer = context._pop_buffer_and_writer()"
)
if callstack:
- self.printer.writeline("context.caller_stack.pop_frame()")
+ self.printer.writeline("context.caller_stack._pop_frame()")
s = "__M_buf.getvalue()"
if filtered:
s = self.create_filter_callable(node.filter_args.args, s, False)
@@ -382,7 +384,7 @@ class _GenerateRenderMethod(object):
self.printer.writeline("return %s" % s)
else:
self.printer.writelines(
- "context.write(%s)" % s,
+ "__M_writer(%s)" % s,
"return ''"
)
@@ -420,7 +422,7 @@ class _GenerateRenderMethod(object):
self.printer.writelines("return " + s,None)
else:
self.printer.writelines(
- "context.write(context.get('local').get_cached(%s, %screatefunc=lambda:__M_%s(%s)))" % (cachekey, ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]), name, ','.join(pass_args)),
+ "__M_writer(context.get('local').get_cached(%s, %screatefunc=lambda:__M_%s(%s)))" % (cachekey, ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]), name, ','.join(pass_args)),
"return ''",
None
)
@@ -460,9 +462,9 @@ class _GenerateRenderMethod(object):
self.write_source_comment(node)
if len(node.escapes) or (self.compiler.pagetag is not None and len(self.compiler.pagetag.filter_args.args)) or len(self.compiler.default_filters):
s = self.create_filter_callable(node.escapes_code.args, "%s" % node.text, True)
- self.printer.writeline("context.write(%s)" % s)
+ self.printer.writeline("__M_writer(%s)" % s)
else:
- self.printer.writeline("context.write(%s)" % node.text)
+ self.printer.writeline("__M_writer(%s)" % node.text)
def visitControlLine(self, node):
if node.isend:
@@ -472,12 +474,12 @@ class _GenerateRenderMethod(object):
self.printer.writeline(node.text)
def visitText(self, node):
self.write_source_comment(node)
- self.printer.writeline("context.write(%s)" % repr(node.content))
+ self.printer.writeline("__M_writer(%s)" % repr(node.content))
def visitTextTag(self, node):
filtered = len(node.filter_args.args) > 0
if filtered:
self.printer.writelines(
- "context.push_buffer()",
+ "__M_writer = context._push_writer()",
"try:",
)
for n in node.nodes:
@@ -485,8 +487,8 @@ class _GenerateRenderMethod(object):
if filtered:
self.printer.writelines(
"finally:",
- "__M_buf = context.pop_buffer()",
- "context.write(%s)" % self.create_filter_callable(node.filter_args.args, "__M_buf.getvalue()", False),
+ "__M_buf, __M_writer = context._pop_buffer_and_writer()",
+ "__M_writer(%s)" % self.create_filter_callable(node.filter_args.args, "__M_buf.getvalue()", False),
None
)
@@ -544,7 +546,7 @@ class _GenerateRenderMethod(object):
buffered = False
if buffered:
self.printer.writelines(
- "context.push_buffer()",
+ "context._push_buffer()",
"try:"
)
self.write_variable_declares(body_identifiers)
@@ -569,7 +571,7 @@ class _GenerateRenderMethod(object):
"try:")
self.write_source_comment(node)
self.printer.writelines(
- "context.write(%s)" % self.create_filter_callable([], node.attributes['expr'], True),
+ "__M_writer(%s)" % self.create_filter_callable([], node.attributes['expr'], True),
"finally:",
"context.caller_stack.nextcaller = None",
None
diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py
index f0ae468..e124b6b 100644
--- a/lib/mako/runtime.py
+++ b/lib/mako/runtime.py
@@ -23,27 +23,61 @@ class Context(object):
# "caller" stack used by def calls with content
self.caller_stack = data['caller'] = CallerStack()
+
lookup = property(lambda self:self._with_template.lookup)
kwargs = property(lambda self:self._kwargs.copy())
+
def push_caller(self, caller):
self.caller_stack.append(caller)
+
def pop_caller(self):
del self.caller_stack[-1]
+
def keys(self):
return self._data.keys()
+
def __getitem__(self, key):
return self._data[key]
- def push_buffer(self):
+
+ def _push_writer(self):
+ """push a capturing buffer onto this Context and return the new Writer function."""
+
+ buf = util.FastEncodingBuffer()
+ self._buffer_stack.append(buf)
+ return buf.write
+
+ def _pop_buffer_and_writer(self):
+ """pop the most recent capturing buffer from this Context
+ and return the current writer after the pop.
+
+ """
+
+ buf = self._buffer_stack.pop()
+ return buf, self._buffer_stack[-1].write
+
+ def _push_buffer(self):
"""push a capturing buffer onto this Context."""
- self._buffer_stack.append(util.FastEncodingBuffer())
- def pop_buffer(self):
+
+ self._push_writer()
+
+ def _pop_buffer(self):
"""pop the most recent capturing buffer from this Context."""
+
return self._buffer_stack.pop()
+
def get(self, key, default=None):
return self._data.get(key, default)
+
def write(self, string):
"""write a string to this Context's underlying output buffer."""
+
self._buffer_stack[-1].write(string)
+
+ def writer(self):
+ """return the current writer function"""
+
+ return self._buffer_stack[-1].write
+
def _copy(self):
c = Context.__new__(Context)
c._buffer_stack = self._buffer_stack
@@ -78,10 +112,10 @@ class CallerStack(list):
return self[-1]
def __getattr__(self, key):
return getattr(self._get_caller(), key)
- def push_frame(self):
+ def _push_frame(self):
self.append(self.nextcaller or None)
self.nextcaller = None
- def pop_frame(self):
+ def _pop_frame(self):
self.nextcaller = self.pop()
@@ -217,22 +251,22 @@ class Namespace(object):
def supports_caller(func):
"""apply a caller_stack compatibility decorator to a plain Python function."""
def wrap_stackframe(context, *args, **kwargs):
- context.caller_stack.push_frame()
+ context.caller_stack._push_frame()
try:
return func(context, *args, **kwargs)
finally:
- context.caller_stack.pop_frame()
+ context.caller_stack._pop_frame()
return wrap_stackframe
def capture(context, callable_, *args, **kwargs):
"""execute the given template def, capturing the output into a buffer."""
if not callable(callable_):
raise exceptions.RuntimeException("capture() function expects a callable as its argument (i.e. capture(func, *args, **kwargs))")
- context.push_buffer()
+ context._push_buffer()
try:
callable_(*args, **kwargs)
finally:
- buf = context.pop_buffer()
+ buf = context._pop_buffer()
return buf.getvalue()
def _include_file(context, uri, calling_uri, **kwargs):
@@ -296,7 +330,7 @@ def _render(template, callable_, args, data, as_unicode=False):
context = Context(buf, **data)
context._with_template = template
_render_context(template, callable_, context, *args, **_kwargs_for_callable(callable_, data))
- return context.pop_buffer().getvalue()
+ return context._pop_buffer().getvalue()
def _kwargs_for_callable(callable_, data, **kwargs):
argspec = inspect.getargspec(callable_)
diff --git a/test/call.py b/test/call.py
index 52ebeb0..0c89694 100644
--- a/test/call.py
+++ b/test/call.py
@@ -105,6 +105,7 @@ class CallTest(unittest.TestCase):
</%call>
""")
+
assert result_lines(t.render()) == [
"<h1>",
"Some title",
@@ -395,6 +396,8 @@ class CallTest(unittest.TestCase):
assert result_lines(t.render()) == ['this is a', 'this is b', 'this is c:', "this is the body in b's call", 'the embedded "d" is:', 'this is d']
class SelfCacheTest(unittest.TestCase):
+ """this test uses a now non-public API."""
+
def test_basic(self):
t = Template("""
<%!
@@ -405,11 +408,11 @@ class SelfCacheTest(unittest.TestCase):
global cached
if cached:
return "cached: " + cached
- context.push_buffer()
+ __M_writer = context._push_writer()
%>
this is foo
<%
- buf = context.pop_buffer()
+ buf, __M_writer = context._pop_buffer_and_writer()
cached = buf.getvalue()
return cached
%>
diff --git a/test/filters.py b/test/filters.py
index da7caa9..4fdd342 100644
--- a/test/filters.py
+++ b/test/filters.py
@@ -34,6 +34,7 @@ class FilterTest(unittest.TestCase):
</%def>
${foo()}
""")
+
assert flatten_result(t.render(x="this is x", myfilter=lambda t: "MYFILTER->%s<-MYFILTER" % t)) == "MYFILTER-> this is foo <-MYFILTER"
def test_import(self):