Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
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):
pass
# defaults we want to override
_JINJA2_ENVIRONMENT_DEFAULTS = {
'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)
else:
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__:
return
f = self.open_if_exists(self.filename)
if f is None:
raise TemplateNotFound(self.filename)
self._mtime = self.getmtime(self.filename)
data = ''
try:
data = f.read()
finally:
f.close()
if not isinstance(data, text_type):
data = data.decode(self.encoding)
self._contents = data
@property
def contents(self):
self._delay_init()
return self._contents
@property
def mtime(self):
self._delay_init()
return self._mtime
def uptodate(self):
try:
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:
break
if f is None:
return None
pname = f.f_globals.get('__name__') or '__main__'
m = sys.modules[pname]
f = getattr(m, '__file__', '')
if (('__init__.py' 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)
else:
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
try:
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(name=ex.name, message=message)
def _parse_config_for_settings(settings):
"""
Generate a dictionary with Jinja2 settings parsed from the config,
settings.
:param settings: configurator registry settings.
:type settings: dict
:return: dictionary to passed into Jinja2 Environment object
:rtype: dict
"""
environ_args = {}
defaults = _JINJA2_ENVIRONMENT_DEFAULTS
# 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',
'newline_sequence'):
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
kw.update(_parse_config_for_settings(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)
else:
warnings.warn(
'reload_templates setting is deprecated, use '
'pyramid.reload_templates instead.',
DeprecationWarning,
2,
)
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:
extensions.append('jinja2.ext.i18n')
# get jinja2 directories
directories = parse_multiline(settings.get('jinja2.directories') or '')
directories = [abspath_from_resource_spec(d, package) for d in directories]
loader = SmartAssetSpecLoader(
directories,
encoding=input_encoding,
debug=debug)
# 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'] = \
FileSystemBytecodeCache(bytecode_caching_directory)
# clear cache on exit
atexit.register(kw['bytecode_cache'].clear)
# should newstyle gettext calls be enabled?
newstyle = asbool(settings.get('jinja2.newstyle', False))
environment = Environment(loader=loader,
auto_reload=reload_templates,
extensions=extensions,
undefined=undefined,
**kw)
# 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', ''))
environment.filters.update(filters)
#add custom jinja2 tests
tests = parse_config(settings.get('jinja2.tests', ''))
environment.tests.update(tests)
registry.registerUtility(environment, IJinja2Environment)
return registry.queryUtility(IJinja2Environment)
class GetTextWrapper(object):
def __init__(self, domain):
self.domain = domain
@property
def localizer(self):
return i18n.get_localizer(get_current_request())
def gettext(self, message):
return self.localizer.translate(message,
domain=self.domain)
def ngettext(self, singular, plural, n):
return self.localizer.pluralize(singular, plural, n,
domain=self.domain)
@implementer(ITemplateRenderer)
class Jinja2TemplateRenderer(object):
'''Renderer for a jinja2 template'''
template = None
def __init__(self, info, environment):
self.info = info
self.environment = environment
def implementation(self):
return self.template
@property
def template(self):
# get template based on searchpaths, then try relavtive one
info = self.info
name = info.name
name_with_package = None
if ':' not in name and getattr(info, 'package', None) is not None:
package = self.info.package
name_with_package = '%s:%s' % (package.__name__, name)
try:
return self.environment.get_template(name)
except TemplateNotFound:
if name_with_package is not None:
return self.environment.get_template(name_with_package)
else:
raise
def __call__(self, value, system):
try:
system.update(value)
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
config.add_jinja2_search_path('anotherpackage:templates/')
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:
env.loader.searchpath.append(abspath_from_resource_spec(folder,
config.package_name))
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
config.add_jinja2_extension(myext)
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)
env.add_extension(ext)
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
config.get_jinja2_environment()
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()
config.include('pyramid_jinja2')
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
path.
- ``add_jinja2_extension``: Add a list of extensions to the Jinja2
environment.
- 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)
_get_or_build_default_environment(config.registry)
Jump to Line
Something went wrong with that request. Please try again.