Skip to content

Instantly share code, notes, and snippets.

@tgs
Created August 5, 2013 21:56
Show Gist options
  • Save tgs/6159958 to your computer and use it in GitHub Desktop.
Save tgs/6159958 to your computer and use it in GitHub Desktop.
Patch to Google Course Builder v1.5.0, adding caching of the bytecode of Jinja2 templates. Change to the root dir of your course builder installation and use patch -p2 < (this file). Version 1.5.1 will probably include this patch.
diff --git a/coursebuilder/common/jinja_utils.py b/coursebuilder/common/jinja_utils.py
index 9854a44..a5501e0 100644
--- a/coursebuilder/common/jinja_utils.py
+++ b/coursebuilder/common/jinja_utils.py
@@ -18,8 +18,10 @@ __author__ = 'John Orr (jorr@google.com)'
import jinja2
from webapp2_extras import i18n
+from models.models import MemcacheManager
import safe_dom
import tags
+from jinja2.bccache import BytecodeCache
def finalize(x):
@@ -61,12 +63,68 @@ def get_gcb_tags_filter(handler):
return gcb_tags
+class ClearableMemcachedBytecodeCache(BytecodeCache):
+ """Requires a fancy memcache client, like Google App Engine's,
+ that supports namespaces. Requires get, set, add, and incr.
+
+ When you call .clear(), the entries previously stored through
+ this object become inaccessible through it, although they
+ are not guaranteed to be evicted from the underlying memcached
+ immediately.
+ """
+
+ def __init__(self, client, namespace=None,
+ prefix='jinja2:bytecode:', timeout=None,
+ ignore_memcache_errors=True):
+ self.namespace = namespace
+ self.client = client
+ self.prefix = prefix
+ self.timeout = timeout
+ self.ignore_memcache_errors = ignore_memcache_errors
+
+ def _get_key(self, bucket):
+ generation_key = self.prefix + '__generation__'
+ generation = '1'
+ if not self.client.add(generation_key, generation, namespace=self.namespace):
+ generation = self.client.get(generation_key,
+ namespace=self.namespace)
+
+ return '%s:%s:%s' % (self.prefix, generation, bucket.key)
+
+ def clear(self):
+ generation_key = self.prefix + '__generation__'
+ if not self.client.incr(generation_key, 1, namespace=self.namespace):
+ self.client.set(generation_key, '1', namespace=self.namespace)
+
+ def load_bytecode(self, bucket):
+ try:
+ code = self.client.get(self._get_key(bucket),
+ namespace=self.namespace)
+ except Exception:
+ if not self.ignore_memcache_errors:
+ raise
+ code = None
+ if code is not None:
+ bucket.bytecode_from_string(code)
+
+ def dump_bytecode(self, bucket):
+ args = (self._get_key(bucket), bucket.bytecode_to_string())
+ if self.timeout is not None:
+ args += (self.timeout,)
+ try:
+ self.client.set(*args, namespace=self.namespace)
+ except Exception:
+ if not self.ignore_memcache_errors:
+ raise
+
+
def get_template(template_name, dirs, locale=None, handler=None):
"""Sets up an environment and gets jinja template."""
jinja_environment = jinja2.Environment(
autoescape=True, finalize=finalize,
extensions=['jinja2.ext.i18n'],
+ bytecode_cache=ClearableMemcachedBytecodeCache(MemcacheManager),
loader=jinja2.FileSystemLoader(dirs))
jinja_environment.filters['js_string'] = js_string
jinja_environment.filters['gcb_tags'] = get_gcb_tags_filter(handler)
diff --git a/coursebuilder/models/models.py b/coursebuilder/models/models.py
index 81e02ec..b5fe999 100644
--- a/coursebuilder/models/models.py
+++ b/coursebuilder/models/models.py
@@ -100,6 +100,14 @@ class MemcacheManager(object):
key, value, ttl, namespace=cls._get_namespace(namespace))
@classmethod
+ def add(cls, key, value, ttl=DEFAULT_CACHE_TTL_SECS, namespace=None):
+ """Adds an item in memcache if memcache is enabled."""
+ if CAN_USE_MEMCACHE.value:
+ CACHE_PUT.inc()
+ return memcache.add(
+ key, value, ttl, namespace=cls._get_namespace(namespace))
+
+ @classmethod
def delete(cls, key, namespace=None):
"""Deletes an item from memcache if memcache is enabled."""
if CAN_USE_MEMCACHE.value:
@@ -110,7 +118,7 @@ class MemcacheManager(object):
def incr(cls, key, delta, namespace=None):
"""Incr an item in memcache if memcache is enabled."""
if CAN_USE_MEMCACHE.value:
- memcache.incr(
+ return memcache.incr(
key, delta,
namespace=cls._get_namespace(namespace), initial_value=0)
diff --git a/coursebuilder/models/vfs.py b/coursebuilder/models/vfs.py
index 648fc56..ff95c84 100644
--- a/coursebuilder/models/vfs.py
+++ b/coursebuilder/models/vfs.py
@@ -156,6 +156,8 @@ class LocalReadOnlyFileSystem(object):
jinja_environment = jinja2.Environment(
autoescape=True, finalize=jinja_utils.finalize,
extensions=['jinja2.ext.i18n'],
+ bytecode_cache=jinja_utils.ClearableMemcachedBytecodeCache(
+ MemcacheManager),
loader=jinja2.FileSystemLoader(physical_dir_names))
jinja_environment.filters['js_string'] = jinja_utils.js_string
@@ -511,6 +513,8 @@ class DatastoreBackedFileSystem(object):
jinja_environment = jinja2.Environment(
autoescape=True, finalize=jinja_utils.finalize,
extensions=['jinja2.ext.i18n'],
+ bytecode_cache=jinja_utils.ClearableMemcachedBytecodeCache(
+ MemcacheManager, namespace=self._ns),
loader=VirtualFileSystemTemplateLoader(
self, self._logical_home_folder, dir_names))
jinja_environment.filters['js_string'] = jinja_utils.js_string
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment