| # -*- 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 fnmatch | 
 | from hashlib import sha1 | 
 | from jinja2.utils import open_if_exists | 
 | from jinja2._compat import BytesIO, pickle, PY2, text_type | 
 |  | 
 |  | 
 | # marshal works better on 3.x, one hack less required | 
 | if not PY2: | 
 |     marshal_dump = marshal.dump | 
 |     marshal_load = marshal.load | 
 | else: | 
 |  | 
 |     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, text_type): | 
 |                 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. | 
 |  | 
 |     .. versionadded:: 2.7 | 
 |        Added support for ignoring memcache errors through the | 
 |        `ignore_memcache_errors` parameter. | 
 |     """ | 
 |  | 
 |     def __init__(self, client, prefix='jinja2/bytecode/', timeout=None, | 
 |                  ignore_memcache_errors=True): | 
 |         self.client = client | 
 |         self.prefix = prefix | 
 |         self.timeout = timeout | 
 |         self.ignore_memcache_errors = ignore_memcache_errors | 
 |  | 
 |     def load_bytecode(self, bucket): | 
 |         try: | 
 |             code = self.client.get(self.prefix + bucket.key) | 
 |         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.prefix + bucket.key, bucket.bytecode_to_string()) | 
 |         if self.timeout is not None: | 
 |             args += (self.timeout,) | 
 |         try: | 
 |             self.client.set(*args) | 
 |         except Exception: | 
 |             if not self.ignore_memcache_errors: | 
 |                 raise |