|
#!/usr/bin/python |
|
# -*- coding: utf-8 -*- |
|
from __future__ import unicode_literals |
|
|
|
import imp |
|
import sys |
|
import os |
|
from google.appengine.ext import ndb |
|
import zipfile |
|
import marshal |
|
import logging |
|
from jinja2 import Environment |
|
from jinja2.exceptions import TemplateNotFound |
|
|
|
# Loading templates from Python modules from datastore or a zip archive in zip_dir |
|
# Based on : https://groups.google.com/group/pocoo-libs/browse_thread/thread/748b0d2024f88f64 |
|
|
|
|
|
def init_env(site_key_id): |
|
""" initialize Jinja environment : using the module loader |
|
JINJACMS_LOADPY in app.yaml env_variables, value : compiled or NONE |
|
zip archive with .pyc, name : <site_key_id>-compiled-templates.zip """ |
|
|
|
if 'JINJACMS_LOADPY' not in os.environ or os.environ['JINJACMS_LOADPY'].lower() in ['', 'none']: |
|
path = None |
|
logging.info('%s RUNTIME_MODULE loader initialized, using Python code objects' % site_key_id) |
|
else: |
|
path = os.path.join(os.path.dirname(sys.modules['appengine_config'].__file__), |
|
os.environ['JINJACMS_LOADPY'], site_key_id + '-compiled-templates.zip') |
|
logging.info('%s RUNTIME_MODULE loader initialized, using zip in %s' % (site_key_id, path)) |
|
|
|
loader = FileSystemModuleLoader(site_key_id, path) |
|
return Environment(auto_reload=False, loader=loader) |
|
|
|
|
|
class ModuleLoader(object): |
|
"""Base mixin class for loaders that use pre-parsed Jinja2 templates stored as Python code. """ |
|
|
|
def get_module(self, environment, template): |
|
raise TemplateNotFound(template) |
|
|
|
def load(self, environment, filename, j_globals=None): |
|
""" Loads a pre-compiled template, stored as Python code in a template module. """ |
|
|
|
if j_globals is None: |
|
j_globals = {'environment': environment} |
|
|
|
t = object.__new__(environment.template_class) |
|
|
|
module = self.get_module(environment, filename) |
|
name, blocks, root, debug_info = module.run(environment, t) |
|
|
|
t.environment = environment |
|
t.globals = j_globals |
|
t.name = name |
|
t.filename = filename |
|
t.blocks = blocks |
|
# render function and module |
|
t.root_render_func = root |
|
t._module = None |
|
|
|
# debug and loader helpers |
|
t._debug_info = debug_info |
|
t._uptodate = lambda: True |
|
return t |
|
|
|
|
|
class FileSystemModuleLoader(ModuleLoader): |
|
""" Load compiled Jinja templates from the datastore (code objects) |
|
or .py from a zip archive for the CMS sites(site_keys) """ |
|
|
|
def __init__(self, site_key_id, path=None): |
|
|
|
self.site_key_id = site_key_id |
|
self.site_key = ndb.Key('CmsSites', site_key_id) |
|
|
|
if path: |
|
try: |
|
self._zf = zipfile.ZipFile(os.path.join(path), 'r') |
|
except IOError, e: |
|
logging.warning('Zip archive not found, path : %s. Using Runtimes datastore.' |
|
' Exception : %s' % (path, str(e))) |
|
self._zf = None |
|
else: |
|
self._zf = None |
|
|
|
# fake module : <site_key_id> |
|
mod = imp.new_module(site_key_id) |
|
mod.__loader__ = self |
|
mod.__file__ = "[fake module %r]" % site_key_id |
|
mod.__path__ = [] |
|
sys.modules[site_key_id] = mod |
|
|
|
def get_module(self, environment, template): |
|
""" Convert the template to a module name and load the code """ |
|
|
|
mod_name = '%s.%s' % (self.site_key_id, template.replace('.html', '') |
|
.replace('.txt', '').replace('/', '.')) |
|
if mod_name in sys.modules: |
|
return sys.modules[mod_name] |
|
|
|
logging.info('load Jinja template module : ' + mod_name) |
|
try: |
|
if self._zf: |
|
# ignore first 8 bytes with magic and timestamp of .pyc |
|
module_code = marshal.loads(self._zf.read(mod_name + '.pyc')[8:]) |
|
else: |
|
module_code = marshal.loads(ndb.Key('Runtimes', template, |
|
parent=self.site_key).get().blob) |
|
module = imp.new_module(mod_name) |
|
exec module_code in module.__dict__ |
|
sys.modules[mod_name] = module |
|
return module |
|
except (ImportError, AttributeError, KeyError): |
|
logging.error('load failed : ' + mod_name) |
|
raise TemplateNotFound(mod_name) |