Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

472 lines (361 sloc) 15.196 kB
import atexit
import inspect
import os
import sys
import warnings
from zope.interface import implementer
from zope.interface import Interface
from jinja2 import Environment
from jinja2 import FileSystemBytecodeCache
from jinja2 import Undefined
from jinja2 import StrictUndefined
from jinja2 import DebugUndefined
from jinja2.exceptions import TemplateNotFound
from jinja2.loaders import FileSystemLoader
from jinja2.utils import import_string
from jinja2.utils import open_if_exists
from pyramid_jinja2.compat import string_types
from pyramid_jinja2.compat import text_type
from pyramid.asset import abspath_from_asset_spec
from pyramid.interfaces import ITemplateRenderer
from pyramid.resource import abspath_from_resource_spec
from pyramid.settings import asbool
from pyramid import i18n
from pyramid.threadlocal import get_current_request
class IJinja2Environment(Interface):
# defaults we want to override
'autoescape': True
def maybe_import_string(val):
if isinstance(val, string_types):
return import_string(val.strip())
return val
def splitlines(s):
return filter(None, [x.strip() for x in s.splitlines()])
def parse_config(config):
Parses config values from .ini file and returns a dictionary with
imported objects
# input must be a string or dict
result = {}
if isinstance(config, string_types):
for f in splitlines(config):
name, impl = f.split('=', 1)
result[name.strip()] = maybe_import_string(impl)
for name, impl in config.items():
result[name] = maybe_import_string(impl)
return result
def parse_multiline(extensions):
if isinstance(extensions, string_types):
extensions = splitlines(extensions)
return list(extensions) # py3
def parse_undefined(undefined):
if undefined == 'strict':
return StrictUndefined
if undefined == 'debug':
return DebugUndefined
return Undefined
class FileInfo(object):
open_if_exists = staticmethod(open_if_exists)
getmtime = staticmethod(os.path.getmtime)
def __init__(self, filename, encoding='utf-8'):
self.filename = filename
self.encoding = encoding
def _delay_init(self):
if '_mtime' in self.__dict__:
f = self.open_if_exists(self.filename)
if f is None:
raise TemplateNotFound(self.filename)
self._mtime = self.getmtime(self.filename)
data = ''
data =
if not isinstance(data, text_type):
data = data.decode(self.encoding)
self._contents = data
def contents(self):
return self._contents
def mtime(self):
return self._mtime
def uptodate(self):
return os.path.getmtime(self.filename) == self.mtime
except OSError:
return False
class _PackageFinder(object):
inspect = staticmethod(inspect)
def caller_package(self, allowed=()):
f = None
for t in self.inspect.stack():
f = t[0]
if f.f_globals.get('__name__') not in allowed:
if f is None:
return None
pname = f.f_globals.get('__name__') or '__main__'
m = sys.modules[pname]
f = getattr(m, '__file__', '')
if (('' in f) or ('__init__$py' in f)): # empty at >>>
return m
pname = m.__name__.rsplit('.', 1)[0]
return sys.modules[pname]
_caller_package = _PackageFinder().caller_package
class SmartAssetSpecLoader(FileSystemLoader):
'''A Jinja2 template loader that knows how to handle
asset specifications.
def __init__(self, searchpath=(), encoding='utf-8', debug=False):
FileSystemLoader.__init__(self, searchpath, encoding)
self.debug = debug
def list_templates(self):
raise TypeError('this loader cannot iterate over all templates')
def _get_asset_source_fileinfo(self, environment, template):
if getattr(environment, '_default_package', None) is not None:
pname = environment._default_package
filename = abspath_from_asset_spec(template, pname)
filename = abspath_from_asset_spec(template)
fileinfo = FileInfo(filename, self.encoding)
return fileinfo
def get_source(self, environment, template):
# keep legacy asset: prefix checking that bypasses
# source path checking altogether
if template.startswith('asset:'):
newtemplate = template.split(':', 1)[1]
fi = self._get_asset_source_fileinfo(environment, newtemplate)
return fi.contents, fi.filename, fi.uptodate
fi = self._get_asset_source_fileinfo(environment, template)
if os.path.isfile(fi.filename):
return fi.contents, fi.filename, fi.uptodate
return FileSystemLoader.get_source(self, environment, template)
except TemplateNotFound:
ex = sys.exc_info()[1] # py2.5-3.2 compat
message = ex.message
message += ('; asset=%s; searchpath=%r'
% (fi.filename, self.searchpath))
raise TemplateNotFound(, message=message)
def _parse_config_for_settings(settings):
Generate a dictionary with Jinja2 settings parsed from the config,
:param settings: configurator registry settings.
:type settings: dict
:return: dictionary to passed into Jinja2 Environment object
:rtype: dict
environ_args = {}
# set up the keys with the defaults
# this ensures that the defaults are still setup if they are not
# specified in the config
environ_args = defaults.copy()
# set string settings
for short_key_name in ('block_start_string', 'block_end_string',
'variable_start_string', 'variable_end_string',
'comment_start_string', 'comment_end_string',
'line_statement_prefix', 'line_comment_prefix',
key_name = 'jinja2.%s' % (short_key_name,)
if key_name in settings:
environ_args[short_key_name] = \
settings.get(key_name, defaults.get(key_name))
# boolean settings
for short_key_name in ('autoescape', 'trim_blocks', 'optimized'):
key_name = 'jinja2.%s' % (short_key_name,)
if key_name in settings:
environ_args[short_key_name] = \
asbool(settings.get(key_name, defaults.get(key_name)))
# integer settings
for short_key_name in ('cache_size',):
key_name = 'jinja2.%s' % (short_key_name,)
if key_name in settings:
environ_args[short_key_name] = \
int(settings.get(key_name, defaults.get(key_name)))
return environ_args
def _get_or_build_default_environment(registry):
environment = registry.queryUtility(IJinja2Environment)
if environment is not None:
return environment
settings = registry.settings
kw = {}
package = _caller_package(('pyramid_jinja2', 'jinja2', 'pyramid.config'))
debug = asbool(settings.get('debug_templates', False))
# get basic environment jinja2 settings
reload_templates = settings.get('reload_templates', None)
if reload_templates is None:
# since version 1.5, both settings are supported
reload_templates = settings.get('pyramid.reload_templates', False)
'reload_templates setting is deprecated, use '
'pyramid.reload_templates instead.',
reload_templates = asbool(reload_templates)
undefined = parse_undefined(settings.get('jinja2.undefined', ''))
# get supplementary junja2 settings
input_encoding = settings.get('jinja2.input_encoding', 'utf-8')
domain = settings.get('jinja2.i18n.domain', 'messages')
# get jinja2 extensions
extensions = parse_multiline(settings.get('jinja2.extensions', ''))
if 'jinja2.ext.i18n' not in extensions:
# get jinja2 directories
directories = parse_multiline(settings.get('jinja2.directories') or '')
directories = [abspath_from_resource_spec(d, package) for d in directories]
loader = SmartAssetSpecLoader(
# get jinja2 bytecode caching settings and set up bytecaching
bytecode_caching = asbool(settings.get('jinja2.bytecode_caching', True))
bytecode_caching_directory = \
settings.get('jinja2.bytecode_caching_directory', None)
if bytecode_caching:
kw['bytecode_cache'] = \
# clear cache on exit
# should newstyle gettext calls be enabled?
newstyle = asbool(settings.get('jinja2.newstyle', False))
environment = Environment(loader=loader,
# register pyramid i18n functions
wrapper = GetTextWrapper(domain=domain)
environment.install_gettext_callables(wrapper.gettext, wrapper.ngettext, newstyle=newstyle)
# register global repository for templates
if package is not None:
environment._default_package = package.__name__
#add custom jinja2 filters
filters = parse_config(settings.get('jinja2.filters', ''))
#add custom jinja2 tests
tests = parse_config(settings.get('jinja2.tests', ''))
registry.registerUtility(environment, IJinja2Environment)
return registry.queryUtility(IJinja2Environment)
class GetTextWrapper(object):
def __init__(self, domain):
self.domain = domain
def localizer(self):
return i18n.get_localizer(get_current_request())
def gettext(self, message):
return self.localizer.translate(message,
def ngettext(self, singular, plural, n):
return self.localizer.pluralize(singular, plural, n,
class Jinja2TemplateRenderer(object):
'''Renderer for a jinja2 template'''
template = None
def __init__(self, info, environment): = info
self.environment = environment
def implementation(self):
return self.template
def template(self):
# get template based on searchpaths, then try relavtive one
info =
name =
name_with_package = None
if ':' not in name and getattr(info, 'package', None) is not None:
package =
name_with_package = '%s:%s' % (package.__name__, name)
return self.environment.get_template(name)
except TemplateNotFound:
if name_with_package is not None:
return self.environment.get_template(name_with_package)
def __call__(self, value, system):
except (TypeError, ValueError):
ex = sys.exc_info()[1] # py2.5 - 3.2 compat
raise ValueError('renderer was passed non-dictionary '
'as value: %s' % str(ex))
return self.template.render(system)
def renderer_factory(info):
environment = _get_or_build_default_environment(info.registry)
return Jinja2TemplateRenderer(info, environment)
def add_jinja2_search_path(config, searchpath):
This function is added as a method of a :term:`Configurator`, and
should not be called directly. Instead it should be called like so after
``pyramid_jinja2`` has been passed to ``config.include``:
.. code-block:: python
It will add the directory or :term:`asset spec` passed as ``searchpath``
to the current search path of the ``jinja2.environment.Environment`` used
by :mod:`pyramid_jinja2`.
registry = config.registry
env = _get_or_build_default_environment(registry)
searchpath = parse_multiline(searchpath)
for folder in searchpath:
def add_jinja2_extension(config, ext):
This function is added as a method of a :term:`Configurator`, and
should not be called directly. Instead it should be called like so after
``pyramid_jinja2`` has been passed to ``config.include``:
.. code-block:: python
It will add the Jinja2 extension passed as ``ext`` to the current
``jinja2.environment.Environment`` used by :mod:`pyramid_jinja2`.
env = _get_or_build_default_environment(config.registry)
def get_jinja2_environment(config):
This function is added as a method of a :term:`Configurator`, and
should not be called directly. Instead it should be called like so after
``pyramid_jinja2`` has been passed to ``config.include``:
.. code-block:: python
It will return the current ``jinja2.environment.Environment`` used by
:mod:`pyramid_jinja2` or ``None`` if no environment has yet been set up.
return config.registry.queryUtility(IJinja2Environment)
def includeme(config):
"""Set up standard configurator registrations. Use via:
.. code-block:: python
config = Configurator()
Once this function has been invoked, the ``.jinja2`` renderer is
available for use in Pyramid and these new directives are available as
methods of the configurator:
- ``add_jinja2_search_path``: Add a search path location to the search
- ``add_jinja2_extension``: Add a list of extensions to the Jinja2
- get_jinja2_environment``: Return the Jinja2 ``environment.Environment``
used by ``pyramid_jinja2``.
config.add_renderer('.jinja2', renderer_factory)
config.add_directive('add_jinja2_search_path', add_jinja2_search_path)
config.add_directive('add_jinja2_extension', add_jinja2_extension)
config.add_directive('get_jinja2_environment', get_jinja2_environment)
Jump to Line
Something went wrong with that request. Please try again.