Index: third_party/jinja2/bccache.py |
diff --git a/third_party/jinja2/bccache.py b/third_party/jinja2/bccache.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0b0ccad1f243c598fc23c84191275d2570c65330 |
--- /dev/null |
+++ b/third_party/jinja2/bccache.py |
@@ -0,0 +1,301 @@ |
+# -*- coding: utf-8 -*- |
+""" |
+ jinja2.bccache |
+ ~~~~~~~~~~~~~~ |
+ |
+ This module implements the bytecode cache system Jinja is optionally |
+ using. This is useful if you have very complex template situations and |
+ the compiliation of all those templates slow down your application too |
+ much. |
+ |
+ Situations where this is useful are often forking web applications that |
+ are initialized on the first request. |
+ |
+ :copyright: (c) 2010 by the Jinja Team. |
+ :license: BSD. |
+""" |
+from os import path, listdir |
+import sys |
+import marshal |
+import tempfile |
+import cPickle as pickle |
+import fnmatch |
+try: |
+ from hashlib import sha1 |
+except ImportError: |
+ from sha import new as sha1 |
+from jinja2.utils import open_if_exists |
+ |
+ |
+# marshal works better on 3.x, one hack less required |
+if sys.version_info > (3, 0): |
+ from io import BytesIO |
+ marshal_dump = marshal.dump |
+ marshal_load = marshal.load |
+else: |
+ from cStringIO import StringIO as BytesIO |
+ |
+ def marshal_dump(code, f): |
+ if isinstance(f, file): |
+ marshal.dump(code, f) |
+ else: |
+ f.write(marshal.dumps(code)) |
+ |
+ def marshal_load(f): |
+ if isinstance(f, file): |
+ return marshal.load(f) |
+ return marshal.loads(f.read()) |
+ |
+ |
+bc_version = 2 |
+ |
+# magic version used to only change with new jinja versions. With 2.6 |
+# we change this to also take Python version changes into account. The |
+# reason for this is that Python tends to segfault if fed earlier bytecode |
+# versions because someone thought it would be a good idea to reuse opcodes |
+# or make Python incompatible with earlier versions. |
+bc_magic = 'j2'.encode('ascii') + \ |
+ pickle.dumps(bc_version, 2) + \ |
+ pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1]) |
+ |
+ |
+class Bucket(object): |
+ """Buckets are used to store the bytecode for one template. It's created |
+ and initialized by the bytecode cache and passed to the loading functions. |
+ |
+ The buckets get an internal checksum from the cache assigned and use this |
+ to automatically reject outdated cache material. Individual bytecode |
+ cache subclasses don't have to care about cache invalidation. |
+ """ |
+ |
+ def __init__(self, environment, key, checksum): |
+ self.environment = environment |
+ self.key = key |
+ self.checksum = checksum |
+ self.reset() |
+ |
+ def reset(self): |
+ """Resets the bucket (unloads the bytecode).""" |
+ self.code = None |
+ |
+ def load_bytecode(self, f): |
+ """Loads bytecode from a file or file like object.""" |
+ # make sure the magic header is correct |
+ magic = f.read(len(bc_magic)) |
+ if magic != bc_magic: |
+ self.reset() |
+ return |
+ # the source code of the file changed, we need to reload |
+ checksum = pickle.load(f) |
+ if self.checksum != checksum: |
+ self.reset() |
+ return |
+ self.code = marshal_load(f) |
+ |
+ def write_bytecode(self, f): |
+ """Dump the bytecode into the file or file like object passed.""" |
+ if self.code is None: |
+ raise TypeError('can\'t write empty bucket') |
+ f.write(bc_magic) |
+ pickle.dump(self.checksum, f, 2) |
+ marshal_dump(self.code, f) |
+ |
+ def bytecode_from_string(self, string): |
+ """Load bytecode from a string.""" |
+ self.load_bytecode(BytesIO(string)) |
+ |
+ def bytecode_to_string(self): |
+ """Return the bytecode as string.""" |
+ out = BytesIO() |
+ self.write_bytecode(out) |
+ return out.getvalue() |
+ |
+ |
+class BytecodeCache(object): |
+ """To implement your own bytecode cache you have to subclass this class |
+ and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of |
+ these methods are passed a :class:`~jinja2.bccache.Bucket`. |
+ |
+ A very basic bytecode cache that saves the bytecode on the file system:: |
+ |
+ from os import path |
+ |
+ class MyCache(BytecodeCache): |
+ |
+ def __init__(self, directory): |
+ self.directory = directory |
+ |
+ def load_bytecode(self, bucket): |
+ filename = path.join(self.directory, bucket.key) |
+ if path.exists(filename): |
+ with open(filename, 'rb') as f: |
+ bucket.load_bytecode(f) |
+ |
+ def dump_bytecode(self, bucket): |
+ filename = path.join(self.directory, bucket.key) |
+ with open(filename, 'wb') as f: |
+ bucket.write_bytecode(f) |
+ |
+ A more advanced version of a filesystem based bytecode cache is part of |
+ Jinja2. |
+ """ |
+ |
+ def load_bytecode(self, bucket): |
+ """Subclasses have to override this method to load bytecode into a |
+ bucket. If they are not able to find code in the cache for the |
+ bucket, it must not do anything. |
+ """ |
+ raise NotImplementedError() |
+ |
+ def dump_bytecode(self, bucket): |
+ """Subclasses have to override this method to write the bytecode |
+ from a bucket back to the cache. If it unable to do so it must not |
+ fail silently but raise an exception. |
+ """ |
+ raise NotImplementedError() |
+ |
+ def clear(self): |
+ """Clears the cache. This method is not used by Jinja2 but should be |
+ implemented to allow applications to clear the bytecode cache used |
+ by a particular environment. |
+ """ |
+ |
+ def get_cache_key(self, name, filename=None): |
+ """Returns the unique hash key for this template name.""" |
+ hash = sha1(name.encode('utf-8')) |
+ if filename is not None: |
+ filename = '|' + filename |
+ if isinstance(filename, unicode): |
+ filename = filename.encode('utf-8') |
+ hash.update(filename) |
+ return hash.hexdigest() |
+ |
+ def get_source_checksum(self, source): |
+ """Returns a checksum for the source.""" |
+ return sha1(source.encode('utf-8')).hexdigest() |
+ |
+ def get_bucket(self, environment, name, filename, source): |
+ """Return a cache bucket for the given template. All arguments are |
+ mandatory but filename may be `None`. |
+ """ |
+ key = self.get_cache_key(name, filename) |
+ checksum = self.get_source_checksum(source) |
+ bucket = Bucket(environment, key, checksum) |
+ self.load_bytecode(bucket) |
+ return bucket |
+ |
+ def set_bucket(self, bucket): |
+ """Put the bucket into the cache.""" |
+ self.dump_bytecode(bucket) |
+ |
+ |
+class FileSystemBytecodeCache(BytecodeCache): |
+ """A bytecode cache that stores bytecode on the filesystem. It accepts |
+ two arguments: The directory where the cache items are stored and a |
+ pattern string that is used to build the filename. |
+ |
+ If no directory is specified the system temporary items folder is used. |
+ |
+ The pattern can be used to have multiple separate caches operate on the |
+ same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s`` |
+ is replaced with the cache key. |
+ |
+ >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache') |
+ |
+ This bytecode cache supports clearing of the cache using the clear method. |
+ """ |
+ |
+ def __init__(self, directory=None, pattern='__jinja2_%s.cache'): |
+ if directory is None: |
+ directory = tempfile.gettempdir() |
+ self.directory = directory |
+ self.pattern = pattern |
+ |
+ def _get_cache_filename(self, bucket): |
+ return path.join(self.directory, self.pattern % bucket.key) |
+ |
+ def load_bytecode(self, bucket): |
+ f = open_if_exists(self._get_cache_filename(bucket), 'rb') |
+ if f is not None: |
+ try: |
+ bucket.load_bytecode(f) |
+ finally: |
+ f.close() |
+ |
+ def dump_bytecode(self, bucket): |
+ f = open(self._get_cache_filename(bucket), 'wb') |
+ try: |
+ bucket.write_bytecode(f) |
+ finally: |
+ f.close() |
+ |
+ def clear(self): |
+ # imported lazily here because google app-engine doesn't support |
+ # write access on the file system and the function does not exist |
+ # normally. |
+ from os import remove |
+ files = fnmatch.filter(listdir(self.directory), self.pattern % '*') |
+ for filename in files: |
+ try: |
+ remove(path.join(self.directory, filename)) |
+ except OSError: |
+ pass |
+ |
+ |
+class MemcachedBytecodeCache(BytecodeCache): |
+ """This class implements a bytecode cache that uses a memcache cache for |
+ storing the information. It does not enforce a specific memcache library |
+ (tummy's memcache or cmemcache) but will accept any class that provides |
+ the minimal interface required. |
+ |
+ Libraries compatible with this class: |
+ |
+ - `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache |
+ - `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_ |
+ - `cmemcache <http://gijsbert.org/cmemcache/>`_ |
+ |
+ (Unfortunately the django cache interface is not compatible because it |
+ does not support storing binary data, only unicode. You can however pass |
+ the underlying cache client to the bytecode cache which is available |
+ as `django.core.cache.cache._client`.) |
+ |
+ The minimal interface for the client passed to the constructor is this: |
+ |
+ .. class:: MinimalClientInterface |
+ |
+ .. method:: set(key, value[, timeout]) |
+ |
+ Stores the bytecode in the cache. `value` is a string and |
+ `timeout` the timeout of the key. If timeout is not provided |
+ a default timeout or no timeout should be assumed, if it's |
+ provided it's an integer with the number of seconds the cache |
+ item should exist. |
+ |
+ .. method:: get(key) |
+ |
+ Returns the value for the cache key. If the item does not |
+ exist in the cache the return value must be `None`. |
+ |
+ The other arguments to the constructor are the prefix for all keys that |
+ is added before the actual cache key and the timeout for the bytecode in |
+ the cache system. We recommend a high (or no) timeout. |
+ |
+ This bytecode cache does not support clearing of used items in the cache. |
+ The clear method is a no-operation function. |
+ """ |
+ |
+ def __init__(self, client, prefix='jinja2/bytecode/', timeout=None): |
+ self.client = client |
+ self.prefix = prefix |
+ self.timeout = timeout |
+ |
+ def load_bytecode(self, bucket): |
+ code = self.client.get(self.prefix + bucket.key) |
+ if code is not None: |
+ bucket.bytecode_from_string(code) |
+ |
+ def dump_bytecode(self, bucket): |
+ args = (self.prefix + bucket.key, bucket.bytecode_to_string()) |
+ if self.timeout is not None: |
+ args += (self.timeout,) |
+ self.client.set(*args) |