mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-25 08:28:23 +01:00
1362 lines
49 KiB
Python
1362 lines
49 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Classes for managing templates and their runtime and compile time
|
|
options.
|
|
"""
|
|
import os
|
|
import sys
|
|
import weakref
|
|
from functools import partial
|
|
from functools import reduce
|
|
|
|
from markupsafe import Markup
|
|
|
|
from . import nodes
|
|
from ._compat import encode_filename
|
|
from ._compat import implements_iterator
|
|
from ._compat import implements_to_string
|
|
from ._compat import iteritems
|
|
from ._compat import PY2
|
|
from ._compat import PYPY
|
|
from ._compat import reraise
|
|
from ._compat import string_types
|
|
from ._compat import text_type
|
|
from .compiler import CodeGenerator
|
|
from .compiler import generate
|
|
from .defaults import BLOCK_END_STRING
|
|
from .defaults import BLOCK_START_STRING
|
|
from .defaults import COMMENT_END_STRING
|
|
from .defaults import COMMENT_START_STRING
|
|
from .defaults import DEFAULT_FILTERS
|
|
from .defaults import DEFAULT_NAMESPACE
|
|
from .defaults import DEFAULT_POLICIES
|
|
from .defaults import DEFAULT_TESTS
|
|
from .defaults import KEEP_TRAILING_NEWLINE
|
|
from .defaults import LINE_COMMENT_PREFIX
|
|
from .defaults import LINE_STATEMENT_PREFIX
|
|
from .defaults import LSTRIP_BLOCKS
|
|
from .defaults import NEWLINE_SEQUENCE
|
|
from .defaults import TRIM_BLOCKS
|
|
from .defaults import VARIABLE_END_STRING
|
|
from .defaults import VARIABLE_START_STRING
|
|
from .exceptions import TemplateNotFound
|
|
from .exceptions import TemplateRuntimeError
|
|
from .exceptions import TemplatesNotFound
|
|
from .exceptions import TemplateSyntaxError
|
|
from .exceptions import UndefinedError
|
|
from .lexer import get_lexer
|
|
from .lexer import TokenStream
|
|
from .nodes import EvalContext
|
|
from .parser import Parser
|
|
from .runtime import Context
|
|
from .runtime import new_context
|
|
from .runtime import Undefined
|
|
from .utils import concat
|
|
from .utils import consume
|
|
from .utils import have_async_gen
|
|
from .utils import import_string
|
|
from .utils import internalcode
|
|
from .utils import LRUCache
|
|
from .utils import missing
|
|
|
|
# for direct template usage we have up to ten living environments
|
|
_spontaneous_environments = LRUCache(10)
|
|
|
|
|
|
def get_spontaneous_environment(cls, *args):
|
|
"""Return a new spontaneous environment. A spontaneous environment
|
|
is used for templates created directly rather than through an
|
|
existing environment.
|
|
|
|
:param cls: Environment class to create.
|
|
:param args: Positional arguments passed to environment.
|
|
"""
|
|
key = (cls, args)
|
|
|
|
try:
|
|
return _spontaneous_environments[key]
|
|
except KeyError:
|
|
_spontaneous_environments[key] = env = cls(*args)
|
|
env.shared = True
|
|
return env
|
|
|
|
|
|
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 copy_cache(cache):
|
|
"""Create an empty copy of the given cache."""
|
|
if cache is None:
|
|
return None
|
|
elif type(cache) is dict:
|
|
return {}
|
|
return LRUCache(cache.capacity)
|
|
|
|
|
|
def load_extensions(environment, extensions):
|
|
"""Load the extensions from the list and bind it to the environment.
|
|
Returns a dict of instantiated environments.
|
|
"""
|
|
result = {}
|
|
for extension in extensions:
|
|
if isinstance(extension, string_types):
|
|
extension = import_string(extension)
|
|
result[extension.identifier] = extension(environment)
|
|
return result
|
|
|
|
|
|
def fail_for_missing_callable(string, name):
|
|
msg = string % name
|
|
if isinstance(name, Undefined):
|
|
try:
|
|
name._fail_with_undefined_error()
|
|
except Exception as e:
|
|
msg = "%s (%s; did you forget to quote the callable name?)" % (msg, e)
|
|
raise TemplateRuntimeError(msg)
|
|
|
|
|
|
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"
|
|
assert environment.newline_sequence in (
|
|
"\r",
|
|
"\r\n",
|
|
"\n",
|
|
), "newline_sequence set to unknown line ending string."
|
|
return environment
|
|
|
|
|
|
class Environment(object):
|
|
r"""The core component of Jinja is the `Environment`. It contains
|
|
important shared variables like configuration, filters, tests,
|
|
globals and others. Instances of this class may be modified if
|
|
they are not shared and if no template was loaded so far.
|
|
Modifications on environments after the first template was loaded
|
|
will lead to surprising effects and undefined behavior.
|
|
|
|
Here are the possible initialization parameters:
|
|
|
|
`block_start_string`
|
|
The string marking the beginning of a block. Defaults to ``'{%'``.
|
|
|
|
`block_end_string`
|
|
The string marking the end of a block. Defaults to ``'%}'``.
|
|
|
|
`variable_start_string`
|
|
The string marking the beginning of a print statement.
|
|
Defaults to ``'{{'``.
|
|
|
|
`variable_end_string`
|
|
The string marking the end of a print statement. Defaults to
|
|
``'}}'``.
|
|
|
|
`comment_start_string`
|
|
The string marking the beginning of a comment. Defaults to ``'{#'``.
|
|
|
|
`comment_end_string`
|
|
The string marking the end of a comment. Defaults to ``'#}'``.
|
|
|
|
`line_statement_prefix`
|
|
If given and a string, this will be used as prefix for line based
|
|
statements. See also :ref:`line-statements`.
|
|
|
|
`line_comment_prefix`
|
|
If given and a string, this will be used as prefix for line based
|
|
comments. See also :ref:`line-statements`.
|
|
|
|
.. versionadded:: 2.2
|
|
|
|
`trim_blocks`
|
|
If this is set to ``True`` the first newline after a block is
|
|
removed (block, not variable tag!). Defaults to `False`.
|
|
|
|
`lstrip_blocks`
|
|
If this is set to ``True`` leading spaces and tabs are stripped
|
|
from the start of a line to a block. Defaults to `False`.
|
|
|
|
`newline_sequence`
|
|
The sequence that starts a newline. Must be one of ``'\r'``,
|
|
``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a
|
|
useful default for Linux and OS X systems as well as web
|
|
applications.
|
|
|
|
`keep_trailing_newline`
|
|
Preserve the trailing newline when rendering templates.
|
|
The default is ``False``, which causes a single newline,
|
|
if present, to be stripped from the end of the template.
|
|
|
|
.. versionadded:: 2.7
|
|
|
|
`extensions`
|
|
List of Jinja extensions to use. This can either be import paths
|
|
as strings or extension classes. For more information have a
|
|
look at :ref:`the extensions documentation <jinja-extensions>`.
|
|
|
|
`optimized`
|
|
should the optimizer be enabled? Default is ``True``.
|
|
|
|
`undefined`
|
|
:class:`Undefined` or a subclass of it that is used to represent
|
|
undefined values in the template.
|
|
|
|
`finalize`
|
|
A callable that can be used to process the result of a variable
|
|
expression before it is output. For example one can convert
|
|
``None`` implicitly into an empty string here.
|
|
|
|
`autoescape`
|
|
If set to ``True`` the XML/HTML autoescaping feature is enabled by
|
|
default. For more details about autoescaping see
|
|
:class:`~markupsafe.Markup`. As of Jinja 2.4 this can also
|
|
be a callable that is passed the template name and has to
|
|
return ``True`` or ``False`` depending on autoescape should be
|
|
enabled by default.
|
|
|
|
.. versionchanged:: 2.4
|
|
`autoescape` can now be a function
|
|
|
|
`loader`
|
|
The template loader for this environment.
|
|
|
|
`cache_size`
|
|
The size of the cache. Per default this is ``400`` which means
|
|
that if more than 400 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.
|
|
|
|
.. versionchanged:: 2.8
|
|
The cache size was increased to 400 from a low 50.
|
|
|
|
`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.
|
|
|
|
`bytecode_cache`
|
|
If set to a bytecode cache object, this object will provide a
|
|
cache for the internal Jinja bytecode so that templates don't
|
|
have to be parsed if they were not changed.
|
|
|
|
See :ref:`bytecode-cache` for more information.
|
|
|
|
`enable_async`
|
|
If set to true this enables async template execution which allows
|
|
you to take advantage of newer Python features. This requires
|
|
Python 3.6 or later.
|
|
"""
|
|
|
|
#: if this environment is sandboxed. Modifying this variable won't make
|
|
#: the environment sandboxed though. For a real sandboxed environment
|
|
#: have a look at jinja2.sandbox. This flag alone controls the code
|
|
#: generation by the compiler.
|
|
sandboxed = False
|
|
|
|
#: True if the environment is just an overlay
|
|
overlayed = 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
|
|
|
|
#: the class that is used for code generation. See
|
|
#: :class:`~jinja2.compiler.CodeGenerator` for more information.
|
|
code_generator_class = CodeGenerator
|
|
|
|
#: the context class thatis used for templates. See
|
|
#: :class:`~jinja2.runtime.Context` for more information.
|
|
context_class = Context
|
|
|
|
def __init__(
|
|
self,
|
|
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,
|
|
line_comment_prefix=LINE_COMMENT_PREFIX,
|
|
trim_blocks=TRIM_BLOCKS,
|
|
lstrip_blocks=LSTRIP_BLOCKS,
|
|
newline_sequence=NEWLINE_SEQUENCE,
|
|
keep_trailing_newline=KEEP_TRAILING_NEWLINE,
|
|
extensions=(),
|
|
optimized=True,
|
|
undefined=Undefined,
|
|
finalize=None,
|
|
autoescape=False,
|
|
loader=None,
|
|
cache_size=400,
|
|
auto_reload=True,
|
|
bytecode_cache=None,
|
|
enable_async=False,
|
|
):
|
|
# !!Important notice!!
|
|
# The constructor accepts quite a few arguments that should be
|
|
# passed by keyword rather than position. However it's important to
|
|
# not change the order of arguments because it's used at least
|
|
# internally in those cases:
|
|
# - spontaneous environments (i18n extension and Template)
|
|
# - unittests
|
|
# If parameter changes are required only add parameters at the end
|
|
# and don't change the arguments (or the defaults!) of the arguments
|
|
# existing already.
|
|
|
|
# lexer / parser information
|
|
self.block_start_string = block_start_string
|
|
self.block_end_string = block_end_string
|
|
self.variable_start_string = variable_start_string
|
|
self.variable_end_string = variable_end_string
|
|
self.comment_start_string = comment_start_string
|
|
self.comment_end_string = comment_end_string
|
|
self.line_statement_prefix = line_statement_prefix
|
|
self.line_comment_prefix = line_comment_prefix
|
|
self.trim_blocks = trim_blocks
|
|
self.lstrip_blocks = lstrip_blocks
|
|
self.newline_sequence = newline_sequence
|
|
self.keep_trailing_newline = keep_trailing_newline
|
|
|
|
# runtime information
|
|
self.undefined = undefined
|
|
self.optimized = optimized
|
|
self.finalize = finalize
|
|
self.autoescape = autoescape
|
|
|
|
# defaults
|
|
self.filters = DEFAULT_FILTERS.copy()
|
|
self.tests = DEFAULT_TESTS.copy()
|
|
self.globals = DEFAULT_NAMESPACE.copy()
|
|
|
|
# set the loader provided
|
|
self.loader = loader
|
|
self.cache = create_cache(cache_size)
|
|
self.bytecode_cache = bytecode_cache
|
|
self.auto_reload = auto_reload
|
|
|
|
# configurable policies
|
|
self.policies = DEFAULT_POLICIES.copy()
|
|
|
|
# load extensions
|
|
self.extensions = load_extensions(self, extensions)
|
|
|
|
self.enable_async = enable_async
|
|
self.is_async = self.enable_async and have_async_gen
|
|
if self.is_async:
|
|
# runs patch_all() to enable async support
|
|
from . import asyncsupport # noqa: F401
|
|
|
|
_environment_sanity_check(self)
|
|
|
|
def add_extension(self, extension):
|
|
"""Adds an extension after the environment was created.
|
|
|
|
.. versionadded:: 2.5
|
|
"""
|
|
self.extensions.update(load_extensions(self, [extension]))
|
|
|
|
def extend(self, **attributes):
|
|
"""Add the items to the instance of the environment if they do not exist
|
|
yet. This is used by :ref:`extensions <writing-extensions>` to register
|
|
callbacks and configuration values without breaking inheritance.
|
|
"""
|
|
for key, value in iteritems(attributes):
|
|
if not hasattr(self, key):
|
|
setattr(self, key, value)
|
|
|
|
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,
|
|
line_comment_prefix=missing,
|
|
trim_blocks=missing,
|
|
lstrip_blocks=missing,
|
|
extensions=missing,
|
|
optimized=missing,
|
|
undefined=missing,
|
|
finalize=missing,
|
|
autoescape=missing,
|
|
loader=missing,
|
|
cache_size=missing,
|
|
auto_reload=missing,
|
|
bytecode_cache=missing,
|
|
):
|
|
"""Create a new overlay environment that shares all the data with the
|
|
current environment except for cache and the overridden attributes.
|
|
Extensions cannot be removed for an overlayed environment. An 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.overlayed = True
|
|
rv.linked_to = self
|
|
|
|
for key, value in iteritems(args):
|
|
if value is not missing:
|
|
setattr(rv, key, value)
|
|
|
|
if cache_size is not missing:
|
|
rv.cache = create_cache(cache_size)
|
|
else:
|
|
rv.cache = copy_cache(self.cache)
|
|
|
|
rv.extensions = {}
|
|
for key, value in iteritems(self.extensions):
|
|
rv.extensions[key] = value.bind(rv)
|
|
if extensions is not missing:
|
|
rv.extensions.update(load_extensions(rv, extensions))
|
|
|
|
return _environment_sanity_check(rv)
|
|
|
|
lexer = property(get_lexer, doc="The lexer for this environment.")
|
|
|
|
def iter_extensions(self):
|
|
"""Iterates over the extensions by priority."""
|
|
return iter(sorted(self.extensions.values(), key=lambda x: x.priority))
|
|
|
|
def getitem(self, obj, argument):
|
|
"""Get an item or attribute of an object but prefer the item."""
|
|
try:
|
|
return obj[argument]
|
|
except (AttributeError, TypeError, LookupError):
|
|
if isinstance(argument, string_types):
|
|
try:
|
|
attr = str(argument)
|
|
except Exception:
|
|
pass
|
|
else:
|
|
try:
|
|
return getattr(obj, attr)
|
|
except AttributeError:
|
|
pass
|
|
return self.undefined(obj=obj, name=argument)
|
|
|
|
def getattr(self, obj, attribute):
|
|
"""Get an item or attribute of an object but prefer the attribute.
|
|
Unlike :meth:`getitem` the attribute *must* be a bytestring.
|
|
"""
|
|
try:
|
|
return getattr(obj, attribute)
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
return obj[attribute]
|
|
except (TypeError, LookupError, AttributeError):
|
|
return self.undefined(obj=obj, name=attribute)
|
|
|
|
def call_filter(
|
|
self, name, value, args=None, kwargs=None, context=None, eval_ctx=None
|
|
):
|
|
"""Invokes a filter on a value the same way the compiler does it.
|
|
|
|
Note that on Python 3 this might return a coroutine in case the
|
|
filter is running from an environment in async mode and the filter
|
|
supports async execution. It's your responsibility to await this
|
|
if needed.
|
|
|
|
.. versionadded:: 2.7
|
|
"""
|
|
func = self.filters.get(name)
|
|
if func is None:
|
|
fail_for_missing_callable("no filter named %r", name)
|
|
args = [value] + list(args or ())
|
|
if getattr(func, "contextfilter", False) is True:
|
|
if context is None:
|
|
raise TemplateRuntimeError(
|
|
"Attempted to invoke context filter without context"
|
|
)
|
|
args.insert(0, context)
|
|
elif getattr(func, "evalcontextfilter", False) is True:
|
|
if eval_ctx is None:
|
|
if context is not None:
|
|
eval_ctx = context.eval_ctx
|
|
else:
|
|
eval_ctx = EvalContext(self)
|
|
args.insert(0, eval_ctx)
|
|
elif getattr(func, "environmentfilter", False) is True:
|
|
args.insert(0, self)
|
|
return func(*args, **(kwargs or {}))
|
|
|
|
def call_test(self, name, value, args=None, kwargs=None):
|
|
"""Invokes a test on a value the same way the compiler does it.
|
|
|
|
.. versionadded:: 2.7
|
|
"""
|
|
func = self.tests.get(name)
|
|
if func is None:
|
|
fail_for_missing_callable("no test named %r", name)
|
|
return func(value, *(args or ()), **(kwargs or {}))
|
|
|
|
@internalcode
|
|
def parse(self, source, name=None, 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.
|
|
|
|
If you are :ref:`developing Jinja extensions <writing-extensions>`
|
|
this gives you a good overview of the node tree generated.
|
|
"""
|
|
try:
|
|
return self._parse(source, name, filename)
|
|
except TemplateSyntaxError:
|
|
self.handle_exception(source=source)
|
|
|
|
def _parse(self, source, name, filename):
|
|
"""Internal parsing function used by `parse` and `compile`."""
|
|
return Parser(self, source, name, encode_filename(filename)).parse()
|
|
|
|
def lex(self, source, name=None, filename=None):
|
|
"""Lex the given sourcecode and return a generator that yields
|
|
tokens as tuples in the form ``(lineno, token_type, value)``.
|
|
This can be useful for :ref:`extension development <writing-extensions>`
|
|
and debugging templates.
|
|
|
|
This does not perform preprocessing. If you want the preprocessing
|
|
of the extensions to be applied you have to filter source through
|
|
the :meth:`preprocess` method.
|
|
"""
|
|
source = text_type(source)
|
|
try:
|
|
return self.lexer.tokeniter(source, name, filename)
|
|
except TemplateSyntaxError:
|
|
self.handle_exception(source=source)
|
|
|
|
def preprocess(self, source, name=None, filename=None):
|
|
"""Preprocesses the source with all extensions. This is automatically
|
|
called for all parsing and compiling methods but *not* for :meth:`lex`
|
|
because there you usually only want the actual source tokenized.
|
|
"""
|
|
return reduce(
|
|
lambda s, e: e.preprocess(s, name, filename),
|
|
self.iter_extensions(),
|
|
text_type(source),
|
|
)
|
|
|
|
def _tokenize(self, source, name, filename=None, state=None):
|
|
"""Called by the parser to do the preprocessing and filtering
|
|
for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
|
|
"""
|
|
source = self.preprocess(source, name, filename)
|
|
stream = self.lexer.tokenize(source, name, filename, state)
|
|
for ext in self.iter_extensions():
|
|
stream = ext.filter_stream(stream)
|
|
if not isinstance(stream, TokenStream):
|
|
stream = TokenStream(stream, name, filename)
|
|
return stream
|
|
|
|
def _generate(self, source, name, filename, defer_init=False):
|
|
"""Internal hook that can be overridden to hook a different generate
|
|
method in.
|
|
|
|
.. versionadded:: 2.5
|
|
"""
|
|
return generate(
|
|
source,
|
|
self,
|
|
name,
|
|
filename,
|
|
defer_init=defer_init,
|
|
optimized=self.optimized,
|
|
)
|
|
|
|
def _compile(self, source, filename):
|
|
"""Internal hook that can be overridden to hook a different compile
|
|
method in.
|
|
|
|
.. versionadded:: 2.5
|
|
"""
|
|
return compile(source, filename, "exec")
|
|
|
|
@internalcode
|
|
def compile(self, source, name=None, filename=None, raw=False, defer_init=False):
|
|
"""Compile a node or template source code. The `name` parameter is
|
|
the load name of the template after it was joined using
|
|
:meth:`join_path` if necessary, not the filename on the file system.
|
|
the `filename` parameter is the estimated filename of the template on
|
|
the file system. If the template came from a database or memory this
|
|
can be omitted.
|
|
|
|
The return value of this method is a python code object. If the `raw`
|
|
parameter is `True` the return value will be a string with python
|
|
code equivalent to the bytecode returned otherwise. This method is
|
|
mainly used internally.
|
|
|
|
`defer_init` is use internally to aid the module code generator. This
|
|
causes the generated code to be able to import without the global
|
|
environment variable to be set.
|
|
|
|
.. versionadded:: 2.4
|
|
`defer_init` parameter added.
|
|
"""
|
|
source_hint = None
|
|
try:
|
|
if isinstance(source, string_types):
|
|
source_hint = source
|
|
source = self._parse(source, name, filename)
|
|
source = self._generate(source, name, filename, defer_init=defer_init)
|
|
if raw:
|
|
return source
|
|
if filename is None:
|
|
filename = "<template>"
|
|
else:
|
|
filename = encode_filename(filename)
|
|
return self._compile(source, filename)
|
|
except TemplateSyntaxError:
|
|
self.handle_exception(source=source_hint)
|
|
|
|
def compile_expression(self, source, undefined_to_none=True):
|
|
"""A handy helper method that returns a callable that accepts keyword
|
|
arguments that appear as variables in the expression. If called it
|
|
returns the result of the expression.
|
|
|
|
This is useful if applications want to use the same rules as Jinja
|
|
in template "configuration files" or similar situations.
|
|
|
|
Example usage:
|
|
|
|
>>> env = Environment()
|
|
>>> expr = env.compile_expression('foo == 42')
|
|
>>> expr(foo=23)
|
|
False
|
|
>>> expr(foo=42)
|
|
True
|
|
|
|
Per default the return value is converted to `None` if the
|
|
expression returns an undefined value. This can be changed
|
|
by setting `undefined_to_none` to `False`.
|
|
|
|
>>> env.compile_expression('var')() is None
|
|
True
|
|
>>> env.compile_expression('var', undefined_to_none=False)()
|
|
Undefined
|
|
|
|
.. versionadded:: 2.1
|
|
"""
|
|
parser = Parser(self, source, state="variable")
|
|
try:
|
|
expr = parser.parse_expression()
|
|
if not parser.stream.eos:
|
|
raise TemplateSyntaxError(
|
|
"chunk after expression", parser.stream.current.lineno, None, None
|
|
)
|
|
expr.set_environment(self)
|
|
except TemplateSyntaxError:
|
|
if sys.exc_info() is not None:
|
|
self.handle_exception(source=source)
|
|
|
|
body = [nodes.Assign(nodes.Name("result", "store"), expr, lineno=1)]
|
|
template = self.from_string(nodes.Template(body, lineno=1))
|
|
return TemplateExpression(template, undefined_to_none)
|
|
|
|
def compile_templates(
|
|
self,
|
|
target,
|
|
extensions=None,
|
|
filter_func=None,
|
|
zip="deflated",
|
|
log_function=None,
|
|
ignore_errors=True,
|
|
py_compile=False,
|
|
):
|
|
"""Finds all the templates the loader can find, compiles them
|
|
and stores them in `target`. If `zip` is `None`, instead of in a
|
|
zipfile, the templates will be stored in a directory.
|
|
By default a deflate zip algorithm is used. To switch to
|
|
the stored algorithm, `zip` can be set to ``'stored'``.
|
|
|
|
`extensions` and `filter_func` are passed to :meth:`list_templates`.
|
|
Each template returned will be compiled to the target folder or
|
|
zipfile.
|
|
|
|
By default template compilation errors are ignored. In case a
|
|
log function is provided, errors are logged. If you want template
|
|
syntax errors to abort the compilation you can set `ignore_errors`
|
|
to `False` and you will get an exception on syntax errors.
|
|
|
|
If `py_compile` is set to `True` .pyc files will be written to the
|
|
target instead of standard .py files. This flag does not do anything
|
|
on pypy and Python 3 where pyc files are not picked up by itself and
|
|
don't give much benefit.
|
|
|
|
.. versionadded:: 2.4
|
|
"""
|
|
from .loaders import ModuleLoader
|
|
|
|
if log_function is None:
|
|
|
|
def log_function(x):
|
|
pass
|
|
|
|
if py_compile:
|
|
if not PY2 or PYPY:
|
|
import warnings
|
|
|
|
warnings.warn(
|
|
"'py_compile=True' has no effect on PyPy or Python"
|
|
" 3 and will be removed in version 3.0",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
py_compile = False
|
|
else:
|
|
import imp
|
|
import marshal
|
|
|
|
py_header = imp.get_magic() + u"\xff\xff\xff\xff".encode("iso-8859-15")
|
|
|
|
# Python 3.3 added a source filesize to the header
|
|
if sys.version_info >= (3, 3):
|
|
py_header += u"\x00\x00\x00\x00".encode("iso-8859-15")
|
|
|
|
def write_file(filename, data):
|
|
if zip:
|
|
info = ZipInfo(filename)
|
|
info.external_attr = 0o755 << 16
|
|
zip_file.writestr(info, data)
|
|
else:
|
|
if isinstance(data, text_type):
|
|
data = data.encode("utf8")
|
|
|
|
with open(os.path.join(target, filename), "wb") as f:
|
|
f.write(data)
|
|
|
|
if zip is not None:
|
|
from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED
|
|
|
|
zip_file = ZipFile(
|
|
target, "w", dict(deflated=ZIP_DEFLATED, stored=ZIP_STORED)[zip]
|
|
)
|
|
log_function('Compiling into Zip archive "%s"' % target)
|
|
else:
|
|
if not os.path.isdir(target):
|
|
os.makedirs(target)
|
|
log_function('Compiling into folder "%s"' % target)
|
|
|
|
try:
|
|
for name in self.list_templates(extensions, filter_func):
|
|
source, filename, _ = self.loader.get_source(self, name)
|
|
try:
|
|
code = self.compile(source, name, filename, True, True)
|
|
except TemplateSyntaxError as e:
|
|
if not ignore_errors:
|
|
raise
|
|
log_function('Could not compile "%s": %s' % (name, e))
|
|
continue
|
|
|
|
filename = ModuleLoader.get_module_filename(name)
|
|
|
|
if py_compile:
|
|
c = self._compile(code, encode_filename(filename))
|
|
write_file(filename + "c", py_header + marshal.dumps(c))
|
|
log_function('Byte-compiled "%s" as %s' % (name, filename + "c"))
|
|
else:
|
|
write_file(filename, code)
|
|
log_function('Compiled "%s" as %s' % (name, filename))
|
|
finally:
|
|
if zip:
|
|
zip_file.close()
|
|
|
|
log_function("Finished compiling templates")
|
|
|
|
def list_templates(self, extensions=None, filter_func=None):
|
|
"""Returns a list of templates for this environment. This requires
|
|
that the loader supports the loader's
|
|
:meth:`~BaseLoader.list_templates` method.
|
|
|
|
If there are other files in the template folder besides the
|
|
actual templates, the returned list can be filtered. There are two
|
|
ways: either `extensions` is set to a list of file extensions for
|
|
templates, or a `filter_func` can be provided which is a callable that
|
|
is passed a template name and should return `True` if it should end up
|
|
in the result list.
|
|
|
|
If the loader does not support that, a :exc:`TypeError` is raised.
|
|
|
|
.. versionadded:: 2.4
|
|
"""
|
|
names = self.loader.list_templates()
|
|
|
|
if extensions is not None:
|
|
if filter_func is not None:
|
|
raise TypeError(
|
|
"either extensions or filter_func can be passed, but not both"
|
|
)
|
|
|
|
def filter_func(x):
|
|
return "." in x and x.rsplit(".", 1)[1] in extensions
|
|
|
|
if filter_func is not None:
|
|
names = [name for name in names if filter_func(name)]
|
|
|
|
return names
|
|
|
|
def handle_exception(self, source=None):
|
|
"""Exception handling helper. This is used internally to either raise
|
|
rewritten exceptions or return a rendered traceback for the template.
|
|
"""
|
|
from .debug import rewrite_traceback_stack
|
|
|
|
reraise(*rewrite_traceback_stack(source=source))
|
|
|
|
def join_path(self, template, parent):
|
|
"""Join a template with the parent. By default all the lookups are
|
|
relative to the loader root so this method returns the `template`
|
|
parameter unchanged, but if the paths should be relative to the
|
|
parent template, this function can be used to calculate the real
|
|
template name.
|
|
|
|
Subclasses may override this method and implement template path
|
|
joining here.
|
|
"""
|
|
return template
|
|
|
|
@internalcode
|
|
def _load_template(self, name, globals):
|
|
if self.loader is None:
|
|
raise TypeError("no loader for this environment specified")
|
|
cache_key = (weakref.ref(self.loader), name)
|
|
if self.cache is not None:
|
|
template = self.cache.get(cache_key)
|
|
if template is not None and (
|
|
not self.auto_reload or template.is_up_to_date
|
|
):
|
|
return template
|
|
template = self.loader.load(self, name, globals)
|
|
if self.cache is not None:
|
|
self.cache[cache_key] = template
|
|
return template
|
|
|
|
@internalcode
|
|
def get_template(self, name, parent=None, globals=None):
|
|
"""Load a template from the loader. If a loader is configured this
|
|
method asks the loader for the template and returns a :class:`Template`.
|
|
If the `parent` parameter is not `None`, :meth:`join_path` is called
|
|
to get the real template name before loading.
|
|
|
|
The `globals` parameter can be used to provide template wide globals.
|
|
These variables are available in the context at render time.
|
|
|
|
If the template does not exist a :exc:`TemplateNotFound` exception is
|
|
raised.
|
|
|
|
.. versionchanged:: 2.4
|
|
If `name` is a :class:`Template` object it is returned from the
|
|
function unchanged.
|
|
"""
|
|
if isinstance(name, Template):
|
|
return name
|
|
if parent is not None:
|
|
name = self.join_path(name, parent)
|
|
return self._load_template(name, self.make_globals(globals))
|
|
|
|
@internalcode
|
|
def select_template(self, names, parent=None, globals=None):
|
|
"""Works like :meth:`get_template` but tries a number of templates
|
|
before it fails. If it cannot find any of the templates, it will
|
|
raise a :exc:`TemplatesNotFound` exception.
|
|
|
|
.. versionchanged:: 2.11
|
|
If names is :class:`Undefined`, an :exc:`UndefinedError` is
|
|
raised instead. If no templates were found and names
|
|
contains :class:`Undefined`, the message is more helpful.
|
|
|
|
.. versionchanged:: 2.4
|
|
If `names` contains a :class:`Template` object it is returned
|
|
from the function unchanged.
|
|
|
|
.. versionadded:: 2.3
|
|
"""
|
|
if isinstance(names, Undefined):
|
|
names._fail_with_undefined_error()
|
|
|
|
if not names:
|
|
raise TemplatesNotFound(
|
|
message=u"Tried to select from an empty list " u"of templates."
|
|
)
|
|
globals = self.make_globals(globals)
|
|
for name in names:
|
|
if isinstance(name, Template):
|
|
return name
|
|
if parent is not None:
|
|
name = self.join_path(name, parent)
|
|
try:
|
|
return self._load_template(name, globals)
|
|
except (TemplateNotFound, UndefinedError):
|
|
pass
|
|
raise TemplatesNotFound(names)
|
|
|
|
@internalcode
|
|
def get_or_select_template(self, template_name_or_list, parent=None, globals=None):
|
|
"""Does a typecheck and dispatches to :meth:`select_template`
|
|
if an iterable of template names is given, otherwise to
|
|
:meth:`get_template`.
|
|
|
|
.. versionadded:: 2.3
|
|
"""
|
|
if isinstance(template_name_or_list, (string_types, Undefined)):
|
|
return self.get_template(template_name_or_list, parent, globals)
|
|
elif isinstance(template_name_or_list, Template):
|
|
return template_name_or_list
|
|
return self.select_template(template_name_or_list, parent, globals)
|
|
|
|
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)
|
|
cls = template_class or self.template_class
|
|
return cls.from_code(self, self.compile(source), globals, None)
|
|
|
|
def make_globals(self, d):
|
|
"""Return a dict for the globals."""
|
|
if not d:
|
|
return self.globals
|
|
return dict(self.globals, **d)
|
|
|
|
|
|
class Template(object):
|
|
"""The central template object. This class represents a compiled template
|
|
and is used to evaluate it.
|
|
|
|
Normally the template object is generated from an :class:`Environment` but
|
|
it also has a constructor that makes it possible to create a template
|
|
instance directly using the constructor. It takes the same arguments as
|
|
the environment constructor but it's not possible to specify a loader.
|
|
|
|
Every template object has a few methods and members that are guaranteed
|
|
to exist. However it's important that a template object should be
|
|
considered immutable. Modifications on the object are not supported.
|
|
|
|
Template objects created from the constructor rather than an environment
|
|
do have an `environment` attribute that points to a temporary environment
|
|
that is probably shared with other templates created with the constructor
|
|
and compatible settings.
|
|
|
|
>>> template = Template('Hello {{ name }}!')
|
|
>>> template.render(name='John Doe') == u'Hello John Doe!'
|
|
True
|
|
>>> stream = template.stream(name='John Doe')
|
|
>>> next(stream) == u'Hello John Doe!'
|
|
True
|
|
>>> next(stream)
|
|
Traceback (most recent call last):
|
|
...
|
|
StopIteration
|
|
"""
|
|
|
|
#: Type of environment to create when creating a template directly
|
|
#: rather than through an existing environment.
|
|
environment_class = Environment
|
|
|
|
def __new__(
|
|
cls,
|
|
source,
|
|
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,
|
|
line_comment_prefix=LINE_COMMENT_PREFIX,
|
|
trim_blocks=TRIM_BLOCKS,
|
|
lstrip_blocks=LSTRIP_BLOCKS,
|
|
newline_sequence=NEWLINE_SEQUENCE,
|
|
keep_trailing_newline=KEEP_TRAILING_NEWLINE,
|
|
extensions=(),
|
|
optimized=True,
|
|
undefined=Undefined,
|
|
finalize=None,
|
|
autoescape=False,
|
|
enable_async=False,
|
|
):
|
|
env = get_spontaneous_environment(
|
|
cls.environment_class,
|
|
block_start_string,
|
|
block_end_string,
|
|
variable_start_string,
|
|
variable_end_string,
|
|
comment_start_string,
|
|
comment_end_string,
|
|
line_statement_prefix,
|
|
line_comment_prefix,
|
|
trim_blocks,
|
|
lstrip_blocks,
|
|
newline_sequence,
|
|
keep_trailing_newline,
|
|
frozenset(extensions),
|
|
optimized,
|
|
undefined,
|
|
finalize,
|
|
autoescape,
|
|
None,
|
|
0,
|
|
False,
|
|
None,
|
|
enable_async,
|
|
)
|
|
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.
|
|
"""
|
|
namespace = {"environment": environment, "__file__": code.co_filename}
|
|
exec(code, namespace)
|
|
rv = cls._from_namespace(environment, namespace, globals)
|
|
rv._uptodate = uptodate
|
|
return rv
|
|
|
|
@classmethod
|
|
def from_module_dict(cls, environment, module_dict, globals):
|
|
"""Creates a template object from a module. This is used by the
|
|
module loader to create a template object.
|
|
|
|
.. versionadded:: 2.4
|
|
"""
|
|
return cls._from_namespace(environment, module_dict, globals)
|
|
|
|
@classmethod
|
|
def _from_namespace(cls, environment, namespace, globals):
|
|
t = object.__new__(cls)
|
|
t.environment = environment
|
|
t.globals = globals
|
|
t.name = namespace["name"]
|
|
t.filename = namespace["__file__"]
|
|
t.blocks = namespace["blocks"]
|
|
|
|
# render function and module
|
|
t.root_render_func = namespace["root"]
|
|
t._module = None
|
|
|
|
# debug and loader helpers
|
|
t._debug_info = namespace["debug_info"]
|
|
t._uptodate = None
|
|
|
|
# store the reference
|
|
namespace["environment"] = environment
|
|
namespace["__jinja_template__"] = t
|
|
|
|
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
|
|
are given the context will be empty. These two calls do the same::
|
|
|
|
template.render(knights='that say nih')
|
|
template.render({'knights': 'that say nih'})
|
|
|
|
This will return the rendered template as unicode string.
|
|
"""
|
|
vars = dict(*args, **kwargs)
|
|
try:
|
|
return concat(self.root_render_func(self.new_context(vars)))
|
|
except Exception:
|
|
self.environment.handle_exception()
|
|
|
|
def render_async(self, *args, **kwargs):
|
|
"""This works similar to :meth:`render` but returns a coroutine
|
|
that when awaited returns the entire rendered template string. This
|
|
requires the async feature to be enabled.
|
|
|
|
Example usage::
|
|
|
|
await template.render_async(knights='that say nih; asynchronously')
|
|
"""
|
|
# see asyncsupport for the actual implementation
|
|
raise NotImplementedError(
|
|
"This feature is not available for this version of Python"
|
|
)
|
|
|
|
def stream(self, *args, **kwargs):
|
|
"""Works exactly like :meth:`generate` but returns a
|
|
:class:`TemplateStream`.
|
|
"""
|
|
return TemplateStream(self.generate(*args, **kwargs))
|
|
|
|
def generate(self, *args, **kwargs):
|
|
"""For very large templates it can be useful to not render the whole
|
|
template at once but evaluate each statement after another and yield
|
|
piece for piece. This method basically does exactly that and returns
|
|
a generator that yields one item after another as unicode strings.
|
|
|
|
It accepts the same arguments as :meth:`render`.
|
|
"""
|
|
vars = dict(*args, **kwargs)
|
|
try:
|
|
for event in self.root_render_func(self.new_context(vars)):
|
|
yield event
|
|
except Exception:
|
|
yield self.environment.handle_exception()
|
|
|
|
def generate_async(self, *args, **kwargs):
|
|
"""An async version of :meth:`generate`. Works very similarly but
|
|
returns an async iterator instead.
|
|
"""
|
|
# see asyncsupport for the actual implementation
|
|
raise NotImplementedError(
|
|
"This feature is not available for this version of Python"
|
|
)
|
|
|
|
def new_context(self, vars=None, shared=False, locals=None):
|
|
"""Create a new :class:`Context` for this template. The vars
|
|
provided will be passed to the template. Per default the globals
|
|
are added to the context. If shared is set to `True` the data
|
|
is passed as is to the context without adding the globals.
|
|
|
|
`locals` can be a dict of local variables for internal usage.
|
|
"""
|
|
return new_context(
|
|
self.environment, self.name, self.blocks, vars, shared, self.globals, locals
|
|
)
|
|
|
|
def make_module(self, vars=None, shared=False, locals=None):
|
|
"""This method works like the :attr:`module` attribute when called
|
|
without arguments but it will evaluate the template on every call
|
|
rather than caching it. It's also possible to provide
|
|
a dict which is then used as context. The arguments are the same
|
|
as for the :meth:`new_context` method.
|
|
"""
|
|
return TemplateModule(self, self.new_context(vars, shared, locals))
|
|
|
|
def make_module_async(self, vars=None, shared=False, locals=None):
|
|
"""As template module creation can invoke template code for
|
|
asynchronous executions this method must be used instead of the
|
|
normal :meth:`make_module` one. Likewise the module attribute
|
|
becomes unavailable in async mode.
|
|
"""
|
|
# see asyncsupport for the actual implementation
|
|
raise NotImplementedError(
|
|
"This feature is not available for this version of Python"
|
|
)
|
|
|
|
@internalcode
|
|
def _get_default_module(self):
|
|
if self._module is not None:
|
|
return self._module
|
|
self._module = rv = self.make_module()
|
|
return rv
|
|
|
|
@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')
|
|
>>> str(t.module)
|
|
'23'
|
|
>>> t.module.foo() == u'42'
|
|
True
|
|
|
|
This attribute is not available if async mode is enabled.
|
|
"""
|
|
return self._get_default_module()
|
|
|
|
def get_corresponding_lineno(self, lineno):
|
|
"""Return the source line number of a line number in the
|
|
generated bytecode as they are not in sync.
|
|
"""
|
|
for template_line, code_line in reversed(self.debug_info):
|
|
if code_line <= lineno:
|
|
return template_line
|
|
return 1
|
|
|
|
@property
|
|
def is_up_to_date(self):
|
|
"""If this variable is `False` there is a newer version available."""
|
|
if self._uptodate is None:
|
|
return True
|
|
return self._uptodate()
|
|
|
|
@property
|
|
def debug_info(self):
|
|
"""The debug info mapping."""
|
|
if self._debug_info:
|
|
return [tuple(map(int, x.split("="))) for x in self._debug_info.split("&")]
|
|
return []
|
|
|
|
def __repr__(self):
|
|
if self.name is None:
|
|
name = "memory:%x" % id(self)
|
|
else:
|
|
name = repr(self.name)
|
|
return "<%s %s>" % (self.__class__.__name__, name)
|
|
|
|
|
|
@implements_to_string
|
|
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, body_stream=None):
|
|
if body_stream is None:
|
|
if context.environment.is_async:
|
|
raise RuntimeError(
|
|
"Async mode requires a body stream "
|
|
"to be passed to a template module. Use "
|
|
"the async methods of the API you are "
|
|
"using."
|
|
)
|
|
body_stream = list(template.root_render_func(context))
|
|
self._body_stream = body_stream
|
|
self.__dict__.update(context.get_exported())
|
|
self.__name__ = template.name
|
|
|
|
def __html__(self):
|
|
return Markup(concat(self._body_stream))
|
|
|
|
def __str__(self):
|
|
return concat(self._body_stream)
|
|
|
|
def __repr__(self):
|
|
if self.__name__ is None:
|
|
name = "memory:%x" % id(self)
|
|
else:
|
|
name = repr(self.__name__)
|
|
return "<%s %s>" % (self.__class__.__name__, name)
|
|
|
|
|
|
class TemplateExpression(object):
|
|
"""The :meth:`jinja2.Environment.compile_expression` method returns an
|
|
instance of this object. It encapsulates the expression-like access
|
|
to the template with an expression it wraps.
|
|
"""
|
|
|
|
def __init__(self, template, undefined_to_none):
|
|
self._template = template
|
|
self._undefined_to_none = undefined_to_none
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
context = self._template.new_context(dict(*args, **kwargs))
|
|
consume(self._template.root_render_func(context))
|
|
rv = context.vars["result"]
|
|
if self._undefined_to_none and isinstance(rv, Undefined):
|
|
rv = None
|
|
return rv
|
|
|
|
|
|
@implements_iterator
|
|
class TemplateStream(object):
|
|
"""A template stream works pretty much like an ordinary python generator
|
|
but it can buffer multiple items to reduce the number of total iterations.
|
|
Per default the output is unbuffered which means that for every unbuffered
|
|
instruction in the template one unicode string is yielded.
|
|
|
|
If buffering is enabled with a buffer size of 5, five items are combined
|
|
into a new unicode string. This is mainly useful if you are streaming
|
|
big templates to a client via WSGI which flushes after each iteration.
|
|
"""
|
|
|
|
def __init__(self, gen):
|
|
self._gen = gen
|
|
self.disable_buffering()
|
|
|
|
def dump(self, fp, encoding=None, errors="strict"):
|
|
"""Dump the complete stream into a file or file-like object.
|
|
Per default unicode strings are written, if you want to encode
|
|
before writing specify an `encoding`.
|
|
|
|
Example usage::
|
|
|
|
Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
|
|
"""
|
|
close = False
|
|
if isinstance(fp, string_types):
|
|
if encoding is None:
|
|
encoding = "utf-8"
|
|
fp = open(fp, "wb")
|
|
close = True
|
|
try:
|
|
if encoding is not None:
|
|
iterable = (x.encode(encoding, errors) for x in self)
|
|
else:
|
|
iterable = self
|
|
if hasattr(fp, "writelines"):
|
|
fp.writelines(iterable)
|
|
else:
|
|
for item in iterable:
|
|
fp.write(item)
|
|
finally:
|
|
if close:
|
|
fp.close()
|
|
|
|
def disable_buffering(self):
|
|
"""Disable the output buffering."""
|
|
self._next = partial(next, self._gen)
|
|
self.buffered = False
|
|
|
|
def _buffered_generator(self, size):
|
|
buf = []
|
|
c_size = 0
|
|
push = buf.append
|
|
|
|
while 1:
|
|
try:
|
|
while c_size < size:
|
|
c = next(self._gen)
|
|
push(c)
|
|
if c:
|
|
c_size += 1
|
|
except StopIteration:
|
|
if not c_size:
|
|
return
|
|
yield concat(buf)
|
|
del buf[:]
|
|
c_size = 0
|
|
|
|
def enable_buffering(self, size=5):
|
|
"""Enable buffering. Buffer `size` items before yielding them."""
|
|
if size <= 1:
|
|
raise ValueError("buffer size too small")
|
|
|
|
self.buffered = True
|
|
self._next = partial(next, self._buffered_generator(size))
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def __next__(self):
|
|
return self._next()
|
|
|
|
|
|
# hook in default template class. if anyone reads this comment: ignore that
|
|
# it's possible to use custom templates ;-)
|
|
Environment.template_class = Template
|