satori/satori/common/templating.py

126 lines
3.6 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Templating module."""
from __future__ import absolute_import
import json
import logging
import jinja2
from jinja2 import sandbox
import six
CODE_CACHE = {}
LOG = logging.getLogger(__name__)
if six.PY3:
StandardError = Exception
class TemplateException(Exception):
"""Error applying template."""
class CompilerCache(jinja2.BytecodeCache):
"""Cache for compiled template code.
This is leveraged when satori is used from within a long-running process,
like a server. It does not speed things up for command-line use.
"""
def load_bytecode(self, bucket):
"""Load compiled code from cache."""
if bucket.key in CODE_CACHE:
bucket.bytecode_from_string(CODE_CACHE[bucket.key])
def dump_bytecode(self, bucket):
"""Write compiled code into cache."""
CODE_CACHE[bucket.key] = bucket.bytecode_to_string()
def do_prepend(value, param='/'):
"""Prepend a string if the passed in string exists.
Example:
The template '{{ root|prepend('/')}}/path';
Called with root undefined renders:
/path
Called with root defined as 'root' renders:
/root/path
"""
if value:
return '%s%s' % (param, value)
else:
return ''
def preserve_linefeeds(value):
"""Escape linefeeds.
To make templates work with both YAML and JSON, escape linefeeds instead of
allowing Jinja to render them.
"""
return value.replace("\n", "\\n").replace("\r", "")
def get_jinja_environment(template, extra_globals=None):
"""Return a sandboxed jinja environment."""
template_map = {'template': template}
env = sandbox.ImmutableSandboxedEnvironment(
loader=jinja2.DictLoader(template_map),
bytecode_cache=CompilerCache())
env.filters['prepend'] = do_prepend
env.filters['preserve'] = preserve_linefeeds
env.globals['json'] = json
if extra_globals:
env.globals.update(extra_globals)
return env
def parse(template, extra_globals=None, **kwargs):
"""Parse template.
:param template: the template contents as a string
:param extra_globals: additional globals to include
:param kwargs: extra arguments are passed to the renderer
"""
env = get_jinja_environment(template, extra_globals=extra_globals)
minimum_kwargs = {
'data': {},
}
minimum_kwargs.update(kwargs)
try:
template = env.get_template('template')
except jinja2.TemplateSyntaxError as exc:
LOG.error(exc, exc_info=True)
error_message = "Template had a syntax error: %s" % exc
raise TemplateException(error_message)
try:
result = template.render(**minimum_kwargs)
#TODO(zns): exceptions in Jinja template sometimes missing traceback
except jinja2.TemplateError as exc:
LOG.error(exc, exc_info=True)
error_message = "Template had an error: %s" % exc
raise TemplateException(error_message)
except StandardError as exc:
LOG.error(exc, exc_info=True)
error_message = "Template rendering failed: %s" % exc
raise TemplateException(error_message)
return result