1 # -*- coding: utf-8 -*- 2 """ 3 jinja2.bccache 4 ~~~~~~~~~~~~~~ 5 6 This module implements the bytecode cache system Jinja is optionally 7 using. This is useful if you have very complex template situations and 8 the compiliation of all those templates slow down your application too 9 much. 10 11 Situations where this is useful are often forking web applications that 12 are initialized on the first request. 13 14 :copyright: (c) 2010 by the Jinja Team. 15 :license: BSD. 16 """ 17 from os import path, listdir 18 import sys 19 import marshal 20 import tempfile 21 import fnmatch 22 from hashlib import sha1 23 from jinja2.utils import open_if_exists 24 from jinja2._compat import BytesIO, pickle, PY2, text_type 25 26 27 # marshal works better on 3.x, one hack less required 28 if not PY2: 29 marshal_dump = marshal.dump 30 marshal_load = marshal.load 31 else: 32 33 def marshal_dump(code, f): 34 if isinstance(f, file): 35 marshal.dump(code, f) 36 else: 37 f.write(marshal.dumps(code)) 38 39 def marshal_load(f): 40 if isinstance(f, file): 41 return marshal.load(f) 42 return marshal.loads(f.read()) 43 44 45 bc_version = 2 46 47 # magic version used to only change with new jinja versions. With 2.6 48 # we change this to also take Python version changes into account. The 49 # reason for this is that Python tends to segfault if fed earlier bytecode 50 # versions because someone thought it would be a good idea to reuse opcodes 51 # or make Python incompatible with earlier versions. 52 bc_magic = 'j2'.encode('ascii') + \ 53 pickle.dumps(bc_version, 2) + \ 54 pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1]) 55 56 57 class Bucket(object): 58 """Buckets are used to store the bytecode for one template. It's created 59 and initialized by the bytecode cache and passed to the loading functions. 60 61 The buckets get an internal checksum from the cache assigned and use this 62 to automatically reject outdated cache material. Individual bytecode 63 cache subclasses don't have to care about cache invalidation. 64 """ 65 66 def __init__(self, environment, key, checksum): 67 self.environment = environment 68 self.key = key 69 self.checksum = checksum 70 self.reset() 71 72 def reset(self): 73 """Resets the bucket (unloads the bytecode).""" 74 self.code = None 75 76 def load_bytecode(self, f): 77 """Loads bytecode from a file or file like object.""" 78 # make sure the magic header is correct 79 magic = f.read(len(bc_magic)) 80 if magic != bc_magic: 81 self.reset() 82 return 83 # the source code of the file changed, we need to reload 84 checksum = pickle.load(f) 85 if self.checksum != checksum: 86 self.reset() 87 return 88 self.code = marshal_load(f) 89 90 def write_bytecode(self, f): 91 """Dump the bytecode into the file or file like object passed.""" 92 if self.code is None: 93 raise TypeError('can\'t write empty bucket') 94 f.write(bc_magic) 95 pickle.dump(self.checksum, f, 2) 96 marshal_dump(self.code, f) 97 98 def bytecode_from_string(self, string): 99 """Load bytecode from a string.""" 100 self.load_bytecode(BytesIO(string)) 101 102 def bytecode_to_string(self): 103 """Return the bytecode as string.""" 104 out = BytesIO() 105 self.write_bytecode(out) 106 return out.getvalue() 107 108 109 class BytecodeCache(object): 110 """To implement your own bytecode cache you have to subclass this class 111 and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of 112 these methods are passed a :class:`~jinja2.bccache.Bucket`. 113 114 A very basic bytecode cache that saves the bytecode on the file system:: 115 116 from os import path 117 118 class MyCache(BytecodeCache): 119 120 def __init__(self, directory): 121 self.directory = directory 122 123 def load_bytecode(self, bucket): 124 filename = path.join(self.directory, bucket.key) 125 if path.exists(filename): 126 with open(filename, 'rb') as f: 127 bucket.load_bytecode(f) 128 129 def dump_bytecode(self, bucket): 130 filename = path.join(self.directory, bucket.key) 131 with open(filename, 'wb') as f: 132 bucket.write_bytecode(f) 133 134 A more advanced version of a filesystem based bytecode cache is part of 135 Jinja2. 136 """ 137 138 def load_bytecode(self, bucket): 139 """Subclasses have to override this method to load bytecode into a 140 bucket. If they are not able to find code in the cache for the 141 bucket, it must not do anything. 142 """ 143 raise NotImplementedError() 144 145 def dump_bytecode(self, bucket): 146 """Subclasses have to override this method to write the bytecode 147 from a bucket back to the cache. If it unable to do so it must not 148 fail silently but raise an exception. 149 """ 150 raise NotImplementedError() 151 152 def clear(self): 153 """Clears the cache. This method is not used by Jinja2 but should be 154 implemented to allow applications to clear the bytecode cache used 155 by a particular environment. 156 """ 157 158 def get_cache_key(self, name, filename=None): 159 """Returns the unique hash key for this template name.""" 160 hash = sha1(name.encode('utf-8')) 161 if filename is not None: 162 filename = '|' + filename 163 if isinstance(filename, text_type): 164 filename = filename.encode('utf-8') 165 hash.update(filename) 166 return hash.hexdigest() 167 168 def get_source_checksum(self, source): 169 """Returns a checksum for the source.""" 170 return sha1(source.encode('utf-8')).hexdigest() 171 172 def get_bucket(self, environment, name, filename, source): 173 """Return a cache bucket for the given template. All arguments are 174 mandatory but filename may be `None`. 175 """ 176 key = self.get_cache_key(name, filename) 177 checksum = self.get_source_checksum(source) 178 bucket = Bucket(environment, key, checksum) 179 self.load_bytecode(bucket) 180 return bucket 181 182 def set_bucket(self, bucket): 183 """Put the bucket into the cache.""" 184 self.dump_bytecode(bucket) 185 186 187 class FileSystemBytecodeCache(BytecodeCache): 188 """A bytecode cache that stores bytecode on the filesystem. It accepts 189 two arguments: The directory where the cache items are stored and a 190 pattern string that is used to build the filename. 191 192 If no directory is specified the system temporary items folder is used. 193 194 The pattern can be used to have multiple separate caches operate on the 195 same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s`` 196 is replaced with the cache key. 197 198 >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache') 199 200 This bytecode cache supports clearing of the cache using the clear method. 201 """ 202 203 def __init__(self, directory=None, pattern='__jinja2_%s.cache'): 204 if directory is None: 205 directory = tempfile.gettempdir() 206 self.directory = directory 207 self.pattern = pattern 208 209 def _get_cache_filename(self, bucket): 210 return path.join(self.directory, self.pattern % bucket.key) 211 212 def load_bytecode(self, bucket): 213 f = open_if_exists(self._get_cache_filename(bucket), 'rb') 214 if f is not None: 215 try: 216 bucket.load_bytecode(f) 217 finally: 218 f.close() 219 220 def dump_bytecode(self, bucket): 221 f = open(self._get_cache_filename(bucket), 'wb') 222 try: 223 bucket.write_bytecode(f) 224 finally: 225 f.close() 226 227 def clear(self): 228 # imported lazily here because google app-engine doesn't support 229 # write access on the file system and the function does not exist 230 # normally. 231 from os import remove 232 files = fnmatch.filter(listdir(self.directory), self.pattern % '*') 233 for filename in files: 234 try: 235 remove(path.join(self.directory, filename)) 236 except OSError: 237 pass 238 239 240 class MemcachedBytecodeCache(BytecodeCache): 241 """This class implements a bytecode cache that uses a memcache cache for 242 storing the information. It does not enforce a specific memcache library 243 (tummy's memcache or cmemcache) but will accept any class that provides 244 the minimal interface required. 245 246 Libraries compatible with this class: 247 248 - `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache 249 - `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_ 250 - `cmemcache <http://gijsbert.org/cmemcache/>`_ 251 252 (Unfortunately the django cache interface is not compatible because it 253 does not support storing binary data, only unicode. You can however pass 254 the underlying cache client to the bytecode cache which is available 255 as `django.core.cache.cache._client`.) 256 257 The minimal interface for the client passed to the constructor is this: 258 259 .. class:: MinimalClientInterface 260 261 .. method:: set(key, value[, timeout]) 262 263 Stores the bytecode in the cache. `value` is a string and 264 `timeout` the timeout of the key. If timeout is not provided 265 a default timeout or no timeout should be assumed, if it's 266 provided it's an integer with the number of seconds the cache 267 item should exist. 268 269 .. method:: get(key) 270 271 Returns the value for the cache key. If the item does not 272 exist in the cache the return value must be `None`. 273 274 The other arguments to the constructor are the prefix for all keys that 275 is added before the actual cache key and the timeout for the bytecode in 276 the cache system. We recommend a high (or no) timeout. 277 278 This bytecode cache does not support clearing of used items in the cache. 279 The clear method is a no-operation function. 280 281 .. versionadded:: 2.7 282 Added support for ignoring memcache errors through the 283 `ignore_memcache_errors` parameter. 284 """ 285 286 def __init__(self, client, prefix='jinja2/bytecode/', timeout=None, 287 ignore_memcache_errors=True): 288 self.client = client 289 self.prefix = prefix 290 self.timeout = timeout 291 self.ignore_memcache_errors = ignore_memcache_errors 292 293 def load_bytecode(self, bucket): 294 try: 295 code = self.client.get(self.prefix + bucket.key) 296 except Exception: 297 if not self.ignore_memcache_errors: 298 raise 299 code = None 300 if code is not None: 301 bucket.bytecode_from_string(code) 302 303 def dump_bytecode(self, bucket): 304 args = (self.prefix + bucket.key, bucket.bytecode_to_string()) 305 if self.timeout is not None: 306 args += (self.timeout,) 307 try: 308 self.client.set(*args) 309 except Exception: 310 if not self.ignore_memcache_errors: 311 raise 312