Skip to content

Commit

Permalink
small performance improvements
Browse files Browse the repository at this point in the history
--HG--
branch : trunk
  • Loading branch information
mitsuhiko committed May 1, 2008
1 parent 7259c76 commit 19cf9c2
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 44 deletions.
6 changes: 3 additions & 3 deletions docs/api.rst
Expand Up @@ -123,13 +123,13 @@ disallows all operations beside testing if it's an undefined object.
The Context
-----------

.. autoclass:: jinja2.runtime.TemplateContext
.. autoclass:: jinja2.runtime.Context
:members: super, get, get_exported, get_all

.. attribute:: parent

A dict of read only, global variables the template looks up. These
can either come from another :class:`TemplateContext`, from the
can either come from another :class:`Context`, from the
:attr:`Environment.globals` or :attr:`Template.globals`. It must not
be altered.

Expand Down Expand Up @@ -279,7 +279,7 @@ enabled::
return result

Context filters work the same just that the first argument is the current
active :class:`TemplateContext` rather then the environment.
active :class:`Context` rather then the environment.


.. _writing-tests:
Expand Down
13 changes: 13 additions & 0 deletions docs/templates.rst
Expand Up @@ -797,6 +797,19 @@ The following functions are available in the global scope by default:
For example, range(4) returns [0, 1, 2, 3]. The end point is omitted!
These are exactly the valid indices for a list of 4 elements.

This is useful to repeat a template block multiple times for example
to fill a list. Imagine you have 7 users in the list but you want to
render three empty items to enforce a height with CSS::

<ul>
{% for user in users %}
<li>{{ user.username }}</li>
{% endfor %}
{% for number in range(10 - users|count) %}
<li class="empty"><span>...</span></li>
{% endfor %}
</ul>

.. function:: lipsum(n=5, html=True, min=20, max=100)

Generates some lorem ipsum for the template. Per default five paragraphs
Expand Down
2 changes: 1 addition & 1 deletion examples/bench.py
Expand Up @@ -305,7 +305,7 @@ def test_spitfire():
stmt='bench()')
sys.stdout.write(' >> %-20s<running>' % test)
sys.stdout.flush()
sys.stdout.write('\r %-20s%.4f seconds\n' % (test, t.timeit(number=20) / 20))
sys.stdout.write('\r %-20s%.4f seconds\n' % (test, t.timeit(number=50) / 50))
sys.stdout.write('-' * 80 + '\n')
sys.stdout.write('''\
WARNING: The results of this benchmark are useless to compare the
Expand Down
13 changes: 7 additions & 6 deletions jinja2/compiler.py
Expand Up @@ -740,7 +740,8 @@ def visit_FromImport(self, node, frame):
self.writeline('if l_%s is missing:' % alias)
self.indent()
self.writeline('l_%s = environment.undefined(%r %% '
'included_template.name)' %
'included_template.name, '
'name=included_template.name)' %
(alias, 'the template %r does not export '
'the requested name ' + repr(name)))
self.outdent()
Expand Down Expand Up @@ -770,11 +771,11 @@ def visit_For(self, node, frame):
# the expression pointing to the parent loop. We make the
# undefined a bit more debug friendly at the same time.
parent_loop = 'loop' in aliases and aliases['loop'] \
or "environment.undefined(%r)" % "'loop' is undefined. " \
'the filter section of a loop as well as the ' \
'else block doesn\'t have access to the special ' \
"'loop' variable of the current loop. Because " \
'there is no parent loop it\'s undefined.'
or "environment.undefined(%r, name='loop')" % "'loop' " \
'is undefined. "the filter section of a loop as well ' \
'as the else block doesn\'t have access to the ' \
"special 'loop' variable of the current loop. " \
"Because there is no parent loop it's undefined."

# if we have an extened loop and a node test, we filter in the
# "outer frame".
Expand Down
43 changes: 21 additions & 22 deletions jinja2/environment.py
Expand Up @@ -5,7 +5,7 @@
Provides a class that holds runtime and parsing time options.
:copyright: 2007 by Armin Ronacher.
:copyright: 2008 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import sys
Expand All @@ -14,7 +14,7 @@
from jinja2.parser import Parser
from jinja2.optimizer import optimize
from jinja2.compiler import generate
from jinja2.runtime import Undefined, TemplateContext, concat
from jinja2.runtime import Undefined, Context, concat
from jinja2.debug import translate_exception
from jinja2.utils import import_string, LRUCache, Markup, missing

Expand Down Expand Up @@ -68,6 +68,7 @@ def _environment_sanity_check(environment):
environment.variable_start_string != \
environment.comment_start_string, 'block, variable and comment ' \
'start strings must be different'
return environment


class Environment(object):
Expand Down Expand Up @@ -148,6 +149,9 @@ class Environment(object):
#: True if the environment is just an overlay
overlay = False

#: the environment this environment is linked to if it is an overlay
linked_to = None

#: shared environments have this set to `True`. A shared environment
#: must not be modified
shared = False
Expand Down Expand Up @@ -250,8 +254,7 @@ def overlay(self, block_start_string=missing, block_end_string=missing,
if extensions is not missing:
rv.extensions.extend(load_extensions(extensions))

_environment_sanity_check(rv)
return rv
return _environment_sanity_check(rv)

@property
def lexer(self):
Expand Down Expand Up @@ -456,22 +459,16 @@ def render(self, *args, **kwargs):
This will return the rendered template as unicode string.
"""
try:
return concat(self.generate(*args, **kwargs))
return concat(self._generate(*args, **kwargs))
except:
# hide the `generate` frame
exc_type, exc_value, tb = sys.exc_info()
raise exc_type, exc_value, tb.tb_next
exc_type, exc_value, tb = translate_exception(sys.exc_info())
raise exc_type, exc_value, tb

def stream(self, *args, **kwargs):
"""Works exactly like :meth:`generate` but returns a
:class:`TemplateStream`.
"""
try:
return TemplateStream(self.generate(*args, **kwargs))
except:
# hide the `generate` frame
exc_type, exc_value, tb = sys.exc_info()
raise exc_type, exc_value, tb.tb_next
return TemplateStream(self.generate(*args, **kwargs))

def generate(self, *args, **kwargs):
"""For very large templates it can be useful to not render the whole
Expand All @@ -481,6 +478,14 @@ def generate(self, *args, **kwargs):
It accepts the same arguments as :meth:`render`.
"""
try:
for item in self._generate(*args, **kwargs):
yield item
except:
exc_type, exc_value, tb = translate_exception(sys.exc_info())
raise exc_type, exc_value, tb

def _generate(self, *args, **kwargs):
# assemble the context
context = dict(*args, **kwargs)

Expand All @@ -498,12 +503,7 @@ def generate(self, *args, **kwargs):
'will lead to unexpected results.' %
(plural, ', '.join(overrides), plural or ' a', plural))

try:
for event in self.root_render_func(self.new_context(context)):
yield event
except:
exc_type, exc_value, tb = translate_exception(sys.exc_info())
raise exc_type, exc_value, tb
return self.root_render_func(self.new_context(context))

def new_context(self, vars=None, shared=False):
"""Create a new template context for this template. The vars
Expand All @@ -519,8 +519,7 @@ def new_context(self, vars=None, shared=False):
parent = vars
else:
parent = dict(self.globals, **vars)
return TemplateContext(self.environment, parent, self.name,
self.blocks)
return Context(self.environment, parent, self.name, self.blocks)

@property
def module(self):
Expand Down
26 changes: 14 additions & 12 deletions jinja2/runtime.py
Expand Up @@ -16,8 +16,8 @@


# these variables are exported to the template runtime
__all__ = ['LoopContext', 'TemplateContext', 'TemplateReference', 'Macro',
'TemplateRuntimeError', 'Markup', 'missing', 'concat', 'escape',
__all__ = ['LoopContext', 'Context', 'TemplateReference', 'Macro', 'Markup',
'TemplateRuntimeError', 'missing', 'concat', 'escape',
'markup_join', 'unicode_join']


Expand Down Expand Up @@ -58,7 +58,7 @@ def unicode_join(*args):
return concat(imap(unicode, args))


class TemplateContext(object):
class Context(object):
"""The template context holds the variables of a template. It stores the
values passed to the template and also the names the template exports.
Creating instances is neither supported nor useful as it's created
Expand Down Expand Up @@ -105,7 +105,8 @@ def super(self, name, current):
raise IndexError()
except LookupError:
return self.environment.undefined('there is no parent block '
'called %r.' % name)
'called %r.' % name,
name='super')
wrap = self.environment.autoescape and Markup or (lambda x: x)
render = lambda: wrap(concat(blocks[pos](self)))
render.__name__ = render.name = name
Expand Down Expand Up @@ -239,22 +240,19 @@ def __init__(self, environment, func, name, arguments, defaults,
self.caller = caller

def __call__(self, *args, **kwargs):
if not self.catch_varargs and len(args) > self._argument_count:
raise TypeError('macro %r takes not more than %d argument(s)' %
(self.name, len(self.arguments)))
arguments = []
for idx, name in enumerate(self.arguments):
try:
value = args[idx]
except IndexError:
except:
try:
value = kwargs.pop(name)
except KeyError:
except:
try:
value = self.defaults[idx - self._argument_count]
except IndexError:
except:
value = self._environment.undefined(
'parameter %r was not provided' % name)
'parameter %r was not provided' % name, name=name)
arguments.append(value)

# it's important that the order of these arguments does not change
Expand All @@ -263,7 +261,8 @@ def __call__(self, *args, **kwargs):
if self.caller:
caller = kwargs.pop('caller', None)
if caller is None:
caller = self._environment.undefined('No caller defined')
caller = self._environment.undefined('No caller defined',
name='caller')
arguments.append(caller)
if self.catch_kwargs:
arguments.append(kwargs)
Expand All @@ -272,6 +271,9 @@ def __call__(self, *args, **kwargs):
(self.name, iter(kwargs).next()))
if self.catch_varargs:
arguments.append(args[self._argument_count:])
elif len(args) > self._argument_count:
raise TypeError('macro %r takes not more than %d argument(s)' %
(self.name, len(self.arguments)))
return self._func(*arguments)

def __repr__(self):
Expand Down

0 comments on commit 19cf9c2

Please sign in to comment.