blob: 7c911880edc87d24a873419aceaafe834271ec2f [file] [log] [blame]
Armin Ronacher4d5bdff2008-09-17 16:19:46 +02001# -*- 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
Armin Ronacher55494e42010-01-22 09:41:48 +010014 :copyright: (c) 2010 by the Jinja Team.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020015 :license: BSD.
16"""
Max Ischenko03f88232008-09-18 16:23:33 +020017from os import path, listdir
Armin Ronacher51db6c92011-01-11 20:53:42 +010018import sys
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020019import marshal
Armin Ronachera816bf42008-09-17 21:28:01 +020020import tempfile
Thomas Waldmann7d295622013-05-18 00:06:22 +020021from six.moves import cPickle as pickle
Armin Ronachera816bf42008-09-17 21:28:01 +020022import fnmatch
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020023try:
24 from hashlib import sha1
25except ImportError:
26 from sha import new as sha1
Armin Ronacherccae0552008-10-05 23:08:58 +020027from jinja2.utils import open_if_exists
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020028
29
Armin Ronacher086174c2011-05-24 17:50:08 +020030# marshal works better on 3.x, one hack less required
31if sys.version_info > (3, 0):
32 from io import BytesIO
33 marshal_dump = marshal.dump
34 marshal_load = marshal.load
35else:
36 from cStringIO import StringIO as BytesIO
37
38 def marshal_dump(code, f):
39 if isinstance(f, file):
40 marshal.dump(code, f)
41 else:
42 f.write(marshal.dumps(code))
43
44 def marshal_load(f):
45 if isinstance(f, file):
46 return marshal.load(f)
47 return marshal.loads(f.read())
48
49
Armin Ronacher51db6c92011-01-11 20:53:42 +010050bc_version = 2
51
52# magic version used to only change with new jinja versions. With 2.6
53# we change this to also take Python version changes into account. The
54# reason for this is that Python tends to segfault if fed earlier bytecode
55# versions because someone thought it would be a good idea to reuse opcodes
56# or make Python incompatible with earlier versions.
57bc_magic = 'j2'.encode('ascii') + \
58 pickle.dumps(bc_version, 2) + \
59 pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1])
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020060
61
62class Bucket(object):
Armin Ronachera816bf42008-09-17 21:28:01 +020063 """Buckets are used to store the bytecode for one template. It's created
64 and initialized by the bytecode cache and passed to the loading functions.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020065
Armin Ronachera816bf42008-09-17 21:28:01 +020066 The buckets get an internal checksum from the cache assigned and use this
67 to automatically reject outdated cache material. Individual bytecode
68 cache subclasses don't have to care about cache invalidation.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020069 """
70
Armin Ronachera816bf42008-09-17 21:28:01 +020071 def __init__(self, environment, key, checksum):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020072 self.environment = environment
73 self.key = key
74 self.checksum = checksum
75 self.reset()
76
77 def reset(self):
Armin Ronachera816bf42008-09-17 21:28:01 +020078 """Resets the bucket (unloads the bytecode)."""
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020079 self.code = None
80
Armin Ronachera816bf42008-09-17 21:28:01 +020081 def load_bytecode(self, f):
82 """Loads bytecode from a file or file like object."""
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020083 # make sure the magic header is correct
84 magic = f.read(len(bc_magic))
85 if magic != bc_magic:
86 self.reset()
87 return
88 # the source code of the file changed, we need to reload
Armin Ronacheraa1d17d2008-09-18 18:09:06 +020089 checksum = pickle.load(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020090 if self.checksum != checksum:
91 self.reset()
92 return
Armin Ronacher086174c2011-05-24 17:50:08 +020093 self.code = marshal_load(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020094
Armin Ronachera816bf42008-09-17 21:28:01 +020095 def write_bytecode(self, f):
96 """Dump the bytecode into the file or file like object passed."""
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020097 if self.code is None:
98 raise TypeError('can\'t write empty bucket')
99 f.write(bc_magic)
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200100 pickle.dump(self.checksum, f, 2)
Marcin Mincereeea4952011-07-12 01:59:54 -0700101 marshal_dump(self.code, f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200102
Armin Ronachera816bf42008-09-17 21:28:01 +0200103 def bytecode_from_string(self, string):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200104 """Load bytecode from a string."""
Armin Ronacher086174c2011-05-24 17:50:08 +0200105 self.load_bytecode(BytesIO(string))
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200106
Armin Ronachera816bf42008-09-17 21:28:01 +0200107 def bytecode_to_string(self):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200108 """Return the bytecode as string."""
Armin Ronacher086174c2011-05-24 17:50:08 +0200109 out = BytesIO()
Armin Ronachera816bf42008-09-17 21:28:01 +0200110 self.write_bytecode(out)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200111 return out.getvalue()
112
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200113
114class BytecodeCache(object):
115 """To implement your own bytecode cache you have to subclass this class
Armin Ronachera816bf42008-09-17 21:28:01 +0200116 and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
117 these methods are passed a :class:`~jinja2.bccache.Bucket`.
118
119 A very basic bytecode cache that saves the bytecode on the file system::
120
121 from os import path
122
123 class MyCache(BytecodeCache):
124
125 def __init__(self, directory):
126 self.directory = directory
127
128 def load_bytecode(self, bucket):
129 filename = path.join(self.directory, bucket.key)
130 if path.exists(filename):
Armin Ronacher0faa8612010-02-09 15:04:51 +0100131 with open(filename, 'rb') as f:
Armin Ronachera816bf42008-09-17 21:28:01 +0200132 bucket.load_bytecode(f)
133
134 def dump_bytecode(self, bucket):
135 filename = path.join(self.directory, bucket.key)
Armin Ronacher0faa8612010-02-09 15:04:51 +0100136 with open(filename, 'wb') as f:
Armin Ronachera816bf42008-09-17 21:28:01 +0200137 bucket.write_bytecode(f)
138
139 A more advanced version of a filesystem based bytecode cache is part of
140 Jinja2.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200141 """
142
Armin Ronachera816bf42008-09-17 21:28:01 +0200143 def load_bytecode(self, bucket):
144 """Subclasses have to override this method to load bytecode into a
145 bucket. If they are not able to find code in the cache for the
146 bucket, it must not do anything.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200147 """
148 raise NotImplementedError()
149
Armin Ronachera816bf42008-09-17 21:28:01 +0200150 def dump_bytecode(self, bucket):
151 """Subclasses have to override this method to write the bytecode
152 from a bucket back to the cache. If it unable to do so it must not
153 fail silently but raise an exception.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200154 """
155 raise NotImplementedError()
156
Armin Ronachera816bf42008-09-17 21:28:01 +0200157 def clear(self):
158 """Clears the cache. This method is not used by Jinja2 but should be
159 implemented to allow applications to clear the bytecode cache used
160 by a particular environment.
161 """
162
163 def get_cache_key(self, name, filename=None):
164 """Returns the unique hash key for this template name."""
165 hash = sha1(name.encode('utf-8'))
166 if filename is not None:
Armin Ronacher086174c2011-05-24 17:50:08 +0200167 filename = '|' + filename
Armin Ronachera816bf42008-09-17 21:28:01 +0200168 if isinstance(filename, unicode):
169 filename = filename.encode('utf-8')
Armin Ronacher086174c2011-05-24 17:50:08 +0200170 hash.update(filename)
Armin Ronachera816bf42008-09-17 21:28:01 +0200171 return hash.hexdigest()
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200172
173 def get_source_checksum(self, source):
Armin Ronachera816bf42008-09-17 21:28:01 +0200174 """Returns a checksum for the source."""
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200175 return sha1(source.encode('utf-8')).hexdigest()
176
Armin Ronachera816bf42008-09-17 21:28:01 +0200177 def get_bucket(self, environment, name, filename, source):
178 """Return a cache bucket for the given template. All arguments are
179 mandatory but filename may be `None`.
180 """
181 key = self.get_cache_key(name, filename)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200182 checksum = self.get_source_checksum(source)
Armin Ronachera816bf42008-09-17 21:28:01 +0200183 bucket = Bucket(environment, key, checksum)
184 self.load_bytecode(bucket)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200185 return bucket
186
Armin Ronachera816bf42008-09-17 21:28:01 +0200187 def set_bucket(self, bucket):
188 """Put the bucket into the cache."""
189 self.dump_bytecode(bucket)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200190
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200191
Armin Ronachera816bf42008-09-17 21:28:01 +0200192class FileSystemBytecodeCache(BytecodeCache):
193 """A bytecode cache that stores bytecode on the filesystem. It accepts
194 two arguments: The directory where the cache items are stored and a
195 pattern string that is used to build the filename.
196
197 If no directory is specified the system temporary items folder is used.
198
199 The pattern can be used to have multiple separate caches operate on the
200 same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
201 is replaced with the cache key.
202
203 >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200204
205 This bytecode cache supports clearing of the cache using the clear method.
Armin Ronachera816bf42008-09-17 21:28:01 +0200206 """
207
208 def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
209 if directory is None:
210 directory = tempfile.gettempdir()
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200211 self.directory = directory
212 self.pattern = pattern
213
214 def _get_cache_filename(self, bucket):
215 return path.join(self.directory, self.pattern % bucket.key)
216
Armin Ronachera816bf42008-09-17 21:28:01 +0200217 def load_bytecode(self, bucket):
Armin Ronacherccae0552008-10-05 23:08:58 +0200218 f = open_if_exists(self._get_cache_filename(bucket), 'rb')
219 if f is not None:
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200220 try:
Armin Ronachera816bf42008-09-17 21:28:01 +0200221 bucket.load_bytecode(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200222 finally:
223 f.close()
224
Armin Ronachera816bf42008-09-17 21:28:01 +0200225 def dump_bytecode(self, bucket):
Armin Ronacher0faa8612010-02-09 15:04:51 +0100226 f = open(self._get_cache_filename(bucket), 'wb')
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200227 try:
Armin Ronachera816bf42008-09-17 21:28:01 +0200228 bucket.write_bytecode(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200229 finally:
230 f.close()
Armin Ronachera816bf42008-09-17 21:28:01 +0200231
232 def clear(self):
Max Ischenko03f88232008-09-18 16:23:33 +0200233 # imported lazily here because google app-engine doesn't support
234 # write access on the file system and the function does not exist
235 # normally.
236 from os import remove
Armin Ronacher2e46a5c2008-09-17 22:25:04 +0200237 files = fnmatch.filter(listdir(self.directory), self.pattern % '*')
238 for filename in files:
Armin Ronachera816bf42008-09-17 21:28:01 +0200239 try:
240 remove(path.join(self.directory, filename))
241 except OSError:
242 pass
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200243
244
245class MemcachedBytecodeCache(BytecodeCache):
246 """This class implements a bytecode cache that uses a memcache cache for
247 storing the information. It does not enforce a specific memcache library
248 (tummy's memcache or cmemcache) but will accept any class that provides
249 the minimal interface required.
250
251 Libraries compatible with this class:
252
253 - `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
254 - `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
255 - `cmemcache <http://gijsbert.org/cmemcache/>`_
256
257 (Unfortunately the django cache interface is not compatible because it
258 does not support storing binary data, only unicode. You can however pass
Georg Brandl3e497b72008-09-19 09:55:17 +0000259 the underlying cache client to the bytecode cache which is available
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200260 as `django.core.cache.cache._client`.)
261
262 The minimal interface for the client passed to the constructor is this:
263
264 .. class:: MinimalClientInterface
265
266 .. method:: set(key, value[, timeout])
267
268 Stores the bytecode in the cache. `value` is a string and
269 `timeout` the timeout of the key. If timeout is not provided
270 a default timeout or no timeout should be assumed, if it's
271 provided it's an integer with the number of seconds the cache
272 item should exist.
273
274 .. method:: get(key)
275
276 Returns the value for the cache key. If the item does not
277 exist in the cache the return value must be `None`.
278
279 The other arguments to the constructor are the prefix for all keys that
280 is added before the actual cache key and the timeout for the bytecode in
281 the cache system. We recommend a high (or no) timeout.
282
283 This bytecode cache does not support clearing of used items in the cache.
284 The clear method is a no-operation function.
285 """
286
287 def __init__(self, client, prefix='jinja2/bytecode/', timeout=None):
288 self.client = client
289 self.prefix = prefix
290 self.timeout = timeout
291
292 def load_bytecode(self, bucket):
293 code = self.client.get(self.prefix + bucket.key)
294 if code is not None:
295 bucket.bytecode_from_string(code)
296
297 def dump_bytecode(self, bucket):
298 args = (self.prefix + bucket.key, bucket.bytecode_to_string())
299 if self.timeout is not None:
300 args += (self.timeout,)
301 self.client.set(*args)