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
+from jinja2.defaults import *
from jinja2.lexer import Lexer
from jinja2.parser import Parser
from jinja2.optimizer import optimize
from jinja2.compiler import generate
-from jinja2.runtime import Undefined, TemplateContext, concat
-from jinja2.debug import translate_exception
-from jinja2.utils import import_string, LRUCache, Markup
-from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
+from jinja2.runtime import Undefined, Context
+from jinja2.debug import translate_exception, translate_syntax_error
+from jinja2.exceptions import TemplateSyntaxError
+from jinja2.utils import import_string, LRUCache, Markup, missing, concat
# for direct template usage we have up to ten living environments
return env
-def template_from_code(environment, code, globals, uptodate=None,
- template_class=None):
- """Generate a new template object from code. It's used in the
- template constructor and the loader `load` implementation.
+def create_cache(size):
+ """Return the cache class for the given size."""
+ if size == 0:
+ return None
+ if size < 0:
+ return {}
+ return LRUCache(size)
+
+
+def load_extensions(environment, extensions):
+ """Load the extensions from the list and bind it to the environment.
+ Returns a new list of instanciated environments.
"""
- t = object.__new__(template_class or environment.template_class)
- namespace = {
- 'environment': environment,
- '__jinja_template__': t
- }
- exec code in namespace
- t.environment = environment
- t.name = namespace['name']
- t.filename = code.co_filename
- t.root_render_func = namespace['root']
- t.blocks = namespace['blocks']
- t.globals = globals
-
- # debug and loader helpers
- t._debug_info = namespace['debug_info']
- t._uptodate = uptodate
-
- return t
+ result = []
+ for extension in extensions:
+ if isinstance(extension, basestring):
+ extension = import_string(extension)
+ result.append(extension(environment))
+ return result
+
+
+def _environment_sanity_check(environment):
+ """Perform a sanity check on the environment."""
+ assert issubclass(environment.undefined, Undefined), 'undefined must ' \
+ 'be a subclass of undefined because filters depend on it.'
+ assert environment.block_start_string != \
+ environment.variable_start_string != \
+ environment.comment_start_string, 'block, variable and comment ' \
+ 'start strings must be different'
+ return environment
class Environment(object):
The string marking the begin of a print statement.
Defaults to ``'{{'``.
+ `variable_stop_string`
+ The string marking the end of a print statement. Defaults to ``'}}'``.
+
`comment_start_string`
The string marking the begin of a comment. Defaults to ``'{#'``.
`line_statement_prefix`
If given and a string, this will be used as prefix for line based
- statements.
+ statements. See also :ref:`line-statements`.
`trim_blocks`
If this is set to ``True`` the first newline after a block is
`loader`
The template loader for this environment.
+
+ `cache_size`
+ The size of the cache. Per default this is ``50`` which means that if
+ more than 50 templates are loaded the loader will clean out the least
+ recently used template. If the cache size is set to ``0`` templates are
+ recompiled all the time, if the cache size is ``-1`` the cache will not
+ be cleaned.
+
+ `auto_reload`
+ Some loaders load templates from locations where the template sources
+ may change (ie: file system or database). If `auto_reload` is set to
+ `True` (default) every time a template is requested the loader checks
+ if the source changed and if yes, it will reload the template. For
+ higher performance it's possible to disable that.
"""
#: if this environment is sandboxed. Modifying this variable won't make
#: have a look at jinja2.sandbox
sandboxed = False
+ #: 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
def __init__(self,
- block_start_string='{%',
- block_end_string='%}',
- variable_start_string='{{',
- variable_end_string='}}',
- comment_start_string='{#',
- comment_end_string='#}',
- line_statement_prefix=None,
+ block_start_string=BLOCK_START_STRING,
+ block_end_string=BLOCK_END_STRING,
+ variable_start_string=VARIABLE_START_STRING,
+ variable_end_string=VARIABLE_END_STRING,
+ comment_start_string=COMMENT_START_STRING,
+ comment_end_string=COMMENT_END_STRING,
+ line_statement_prefix=LINE_STATEMENT_PREFIX,
trim_blocks=False,
extensions=(),
optimized=True,
undefined=Undefined,
finalize=None,
autoescape=False,
- loader=None):
+ loader=None,
+ cache_size=50,
+ auto_reload=True):
# !!Important notice!!
# The constructor accepts quite a few arguments that should be
# passed by keyword rather than position. However it's important to
# - unittests
# If parameter changes are required only add parameters at the end
# and don't change the arguments (or the defaults!) of the arguments
- # up to (but excluding) loader.
-
- # santity checks
- assert issubclass(undefined, Undefined), 'undefined must be ' \
- 'a subclass of undefined because filters depend on it.'
- assert block_start_string != variable_start_string != \
- comment_start_string, 'block, variable and comment ' \
- 'start strings must be different'
+ # existing already.
# lexer / parser information
self.block_start_string = block_start_string
# set the loader provided
self.loader = loader
-
- # create lexer
- self.lexer = Lexer(self)
+ self.cache = create_cache(cache_size)
+ self.auto_reload = auto_reload
# load extensions
- self.extensions = []
- for extension in extensions:
- if isinstance(extension, basestring):
- extension = import_string(extension)
- self.extensions.append(extension(self))
+ self.extensions = load_extensions(self, extensions)
+
+ _environment_sanity_check(self)
+
+ def overlay(self, block_start_string=missing, block_end_string=missing,
+ variable_start_string=missing, variable_end_string=missing,
+ comment_start_string=missing, comment_end_string=missing,
+ line_statement_prefix=missing, trim_blocks=missing,
+ extensions=missing, optimized=missing, undefined=missing,
+ finalize=missing, autoescape=missing, loader=missing,
+ cache_size=missing, auto_reload=missing):
+ """Create a new overlay environment that shares all the data with the
+ current environment except of cache and the overriden attributes.
+ Extensions cannot be removed for a overlayed environment. A overlayed
+ environment automatically gets all the extensions of the environment it
+ is linked to plus optional extra extensions.
+
+ Creating overlays should happen after the initial environment was set
+ up completely. Not all attributes are truly linked, some are just
+ copied over so modifications on the original environment may not shine
+ through.
+ """
+ args = dict(locals())
+ del args['self'], args['cache_size'], args['extensions']
+
+ rv = object.__new__(self.__class__)
+ rv.__dict__.update(self.__dict__)
+ rv.overlay = True
+ rv.linked_to = self
+
+ for key, value in args.iteritems():
+ if value is not missing:
+ setattr(rv, key, value)
+
+ if cache_size is not missing:
+ rv.cache = create_cache(cache_size)
+
+ rv.extensions = []
+ for extension in self.extensions:
+ rv.extensions.append(extension.bind(self))
+ if extensions is not missing:
+ rv.extensions.extend(load_extensions(extensions))
+
+ return _environment_sanity_check(rv)
+
+ @property
+ def lexer(self):
+ """Return a fresh lexer for the environment."""
+ return Lexer(self)
def subscribe(self, obj, argument):
"""Get an item or attribute of an object."""
except (TypeError, LookupError):
return self.undefined(obj=obj, name=argument)
- def parse(self, source, name=None):
+ def parse(self, source, filename=None):
"""Parse the sourcecode and return the abstract syntax tree. This
tree of nodes is used by the compiler to convert the template into
executable source- or bytecode. This is useful for debugging or to
extract information from templates.
"""
- return Parser(self, source, name).parse()
+ try:
+ return Parser(self, source, filename).parse()
+ except TemplateSyntaxError, e:
+ exc_type, exc_value, tb = translate_syntax_error(e)
+ raise exc_type, exc_value, tb
def lex(self, source, name=None):
"""Lex the given sourcecode and return a generator that yields
mainly used internally.
"""
if isinstance(source, basestring):
- source = self.parse(source, name)
+ source = self.parse(source, filename)
if self.optimized:
node = optimize(source, self, globals or {})
source = generate(node, self, name, filename)
raise TypeError('no loader for this environment specified')
if parent is not None:
name = self.join_path(name, parent)
- return self.loader.load(self, name, self.make_globals(globals))
+
+ if self.cache is not None:
+ template = self.cache.get(name)
+ if template is not None and (not self.auto_reload or \
+ template.is_up_to_date):
+ return template
+
+ template = self.loader.load(self, name, self.make_globals(globals))
+ if self.cache is not None:
+ self.cache[name] = template
+ return template
def from_string(self, source, globals=None, template_class=None):
"""Load a template from a string. This parses the source given and
returns a :class:`Template` object.
"""
globals = self.make_globals(globals)
- return template_from_code(self, self.compile(source, globals=globals),
- globals, None, template_class)
+ cls = template_class or self.template_class
+ return cls.from_code(self, self.compile(source, globals=globals),
+ globals, None)
def make_globals(self, d):
"""Return a dict for the globals."""
block_start_string, block_end_string, variable_start_string,
variable_end_string, comment_start_string, comment_end_string,
line_statement_prefix, trim_blocks, tuple(extensions), optimized,
- undefined, finalize, autoescape)
+ undefined, finalize, autoescape, None, 0, False)
return env.from_string(source, template_class=cls)
+ @classmethod
+ def from_code(cls, environment, code, globals, uptodate=None):
+ """Creates a template object from compiled code and the globals. This
+ is used by the loaders and environment to create a template object.
+ """
+ t = object.__new__(cls)
+ namespace = {
+ 'environment': environment,
+ '__jinja_template__': t
+ }
+ exec code in namespace
+ t.environment = environment
+ t.name = namespace['name']
+ t.filename = code.co_filename
+ t.root_render_func = namespace['root']
+ t.blocks = namespace['blocks']
+ t.globals = globals
+
+ # debug and loader helpers
+ t._debug_info = namespace['debug_info']
+ t._uptodate = uptodate
+
+ return t
+
def render(self, *args, **kwargs):
"""This method accepts the same arguments as the `dict` constructor:
A dict, a dict subclass or some keyword arguments. If no arguments
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
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)
'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
parent = vars
else:
parent = dict(self.globals, **vars)
- return TemplateContext(self.environment, parent, self.name,
- self.blocks)
-
- def include(self, vars=None):
- """Some templates may export macros or other variables. It's possible
- to access those variables by "including" the template. This is mainly
- used internally but may also be useful on the Python layer. If passed
- a context, the template is evaluated in it, otherwise an empty context
- with just the globals is used.
-
- The return value is an included template object. Converting it to
- unicode returns the rendered contents of the template, the exported
- variables are accessable via the attribute syntax.
-
- This example shows how it can be used:
+ return Context(self.environment, parent, self.name, self.blocks)
+
+ def make_module(self, vars=None, shared=False):
+ """This method works like the :attr:`module` attribute when called
+ without arguments but it will evaluate the template every call
+ rather then caching the template. It's also possible to provide
+ a dict which is then used as context. The arguments are the same
+ as fo the :meth:`new_context` method.
+ """
+ return TemplateModule(self, self.new_context(vars, shared))
- >>> t = Template('{% say_hello(name) %}Hello {{ name }}!{% endmacro %}42')
- >>> i = t.include()
- >>> unicode(i)
+ @property
+ def module(self):
+ """The template as module. This is used for imports in the
+ template runtime but is also useful if one wants to access
+ exported template variables from the Python layer:
+
+ >>> t = Template('{% macro foo() %}42{% endmacro %}23')
+ >>> unicode(t.module)
+ u'23'
+ >>> t.module.foo()
u'42'
- >>> i.say_hello('John')
- u'Hello John!'
"""
- if isinstance(vars, TemplateContext):
- context = TemplateContext(self.environment, vars.parent,
- self.name, self.blocks)
- else:
- context = self.new_context(vars)
- return IncludedTemplate(self, context)
+ if hasattr(self, '_module'):
+ return self._module
+ self._module = rv = self.make_module()
+ return rv
def get_corresponding_lineno(self, lineno):
"""Return the source line number of a line number in the
return '<%s %s>' % (self.__class__.__name__, name)
-class IncludedTemplate(object):
- """Represents an included template. All the exported names of the
+class TemplateModule(object):
+ """Represents an imported template. All the exported names of the
template are available as attributes on this object. Additionally
converting it into an unicode- or bytestrings renders the contents.
"""
def __init__(self, template, context):
- self.__body_stream = tuple(template.root_render_func(context))
+ # don't alter this attribute unless you change it in the
+ # compiler too. The Include without context passing directly
+ # uses the mangled name. The reason why we use a mangled one
+ # is to avoid name clashes with macros with those names.
+ self.__body_stream = list(template.root_render_func(context))
self.__dict__.update(context.get_exported())
self.__name__ = template.name
c_size += 1
except StopIteration:
if not c_size:
- raise
+ return
yield concat(buf)
del buf[:]
c_size = 0