| 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)
|
|
|