blob: 0f7f566c95cba96a350b5284e0382397ac7ced80 [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
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020021import cPickle as pickle
Armin Ronachera816bf42008-09-17 21:28:01 +020022import fnmatch
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020023from cStringIO import StringIO
24try:
25 from hashlib import sha1
26except ImportError:
27 from sha import new as sha1
Armin Ronacherccae0552008-10-05 23:08:58 +020028from jinja2.utils import open_if_exists
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020029
30
Armin Ronacher51db6c92011-01-11 20:53:42 +010031bc_version = 2
32
33# magic version used to only change with new jinja versions. With 2.6
34# we change this to also take Python version changes into account. The
35# reason for this is that Python tends to segfault if fed earlier bytecode
36# versions because someone thought it would be a good idea to reuse opcodes
37# or make Python incompatible with earlier versions.
38bc_magic = 'j2'.encode('ascii') + \
39 pickle.dumps(bc_version, 2) + \
40 pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1])
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020041
42
43class Bucket(object):
Armin Ronachera816bf42008-09-17 21:28:01 +020044 """Buckets are used to store the bytecode for one template. It's created
45 and initialized by the bytecode cache and passed to the loading functions.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020046
Armin Ronachera816bf42008-09-17 21:28:01 +020047 The buckets get an internal checksum from the cache assigned and use this
48 to automatically reject outdated cache material. Individual bytecode
49 cache subclasses don't have to care about cache invalidation.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020050 """
51
Armin Ronachera816bf42008-09-17 21:28:01 +020052 def __init__(self, environment, key, checksum):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020053 self.environment = environment
54 self.key = key
55 self.checksum = checksum
56 self.reset()
57
58 def reset(self):
Armin Ronachera816bf42008-09-17 21:28:01 +020059 """Resets the bucket (unloads the bytecode)."""
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020060 self.code = None
61
Armin Ronachera816bf42008-09-17 21:28:01 +020062 def load_bytecode(self, f):
63 """Loads bytecode from a file or file like object."""
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020064 # make sure the magic header is correct
65 magic = f.read(len(bc_magic))
66 if magic != bc_magic:
67 self.reset()
68 return
69 # the source code of the file changed, we need to reload
Armin Ronacheraa1d17d2008-09-18 18:09:06 +020070 checksum = pickle.load(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020071 if self.checksum != checksum:
72 self.reset()
73 return
Armin Ronacheraa1d17d2008-09-18 18:09:06 +020074 # now load the code. Because marshal is not able to load
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020075 # from arbitrary streams we have to work around that
76 if isinstance(f, file):
Armin Ronacheraa1d17d2008-09-18 18:09:06 +020077 self.code = marshal.load(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020078 else:
79 self.code = marshal.loads(f.read())
80
Armin Ronachera816bf42008-09-17 21:28:01 +020081 def write_bytecode(self, f):
82 """Dump the bytecode into the file or file like object passed."""
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020083 if self.code is None:
84 raise TypeError('can\'t write empty bucket')
85 f.write(bc_magic)
Armin Ronacheraa1d17d2008-09-18 18:09:06 +020086 pickle.dump(self.checksum, f, 2)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020087 if isinstance(f, file):
Armin Ronacheraa1d17d2008-09-18 18:09:06 +020088 marshal.dump(self.code, f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020089 else:
90 f.write(marshal.dumps(self.code))
91
Armin Ronachera816bf42008-09-17 21:28:01 +020092 def bytecode_from_string(self, string):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020093 """Load bytecode from a string."""
Armin Ronachera816bf42008-09-17 21:28:01 +020094 self.load_bytecode(StringIO(string))
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020095
Armin Ronachera816bf42008-09-17 21:28:01 +020096 def bytecode_to_string(self):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020097 """Return the bytecode as string."""
98 out = StringIO()
Armin Ronachera816bf42008-09-17 21:28:01 +020099 self.write_bytecode(out)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200100 return out.getvalue()
101
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200102
103class BytecodeCache(object):
104 """To implement your own bytecode cache you have to subclass this class
Armin Ronachera816bf42008-09-17 21:28:01 +0200105 and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
106 these methods are passed a :class:`~jinja2.bccache.Bucket`.
107
108 A very basic bytecode cache that saves the bytecode on the file system::
109
110 from os import path
111
112 class MyCache(BytecodeCache):
113
114 def __init__(self, directory):
115 self.directory = directory
116
117 def load_bytecode(self, bucket):
118 filename = path.join(self.directory, bucket.key)
119 if path.exists(filename):
Armin Ronacher0faa8612010-02-09 15:04:51 +0100120 with open(filename, 'rb') as f:
Armin Ronachera816bf42008-09-17 21:28:01 +0200121 bucket.load_bytecode(f)
122
123 def dump_bytecode(self, bucket):
124 filename = path.join(self.directory, bucket.key)
Armin Ronacher0faa8612010-02-09 15:04:51 +0100125 with open(filename, 'wb') as f:
Armin Ronachera816bf42008-09-17 21:28:01 +0200126 bucket.write_bytecode(f)
127
128 A more advanced version of a filesystem based bytecode cache is part of
129 Jinja2.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200130 """
131
Armin Ronachera816bf42008-09-17 21:28:01 +0200132 def load_bytecode(self, bucket):
133 """Subclasses have to override this method to load bytecode into a
134 bucket. If they are not able to find code in the cache for the
135 bucket, it must not do anything.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200136 """
137 raise NotImplementedError()
138
Armin Ronachera816bf42008-09-17 21:28:01 +0200139 def dump_bytecode(self, bucket):
140 """Subclasses have to override this method to write the bytecode
141 from a bucket back to the cache. If it unable to do so it must not
142 fail silently but raise an exception.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200143 """
144 raise NotImplementedError()
145
Armin Ronachera816bf42008-09-17 21:28:01 +0200146 def clear(self):
147 """Clears the cache. This method is not used by Jinja2 but should be
148 implemented to allow applications to clear the bytecode cache used
149 by a particular environment.
150 """
151
152 def get_cache_key(self, name, filename=None):
153 """Returns the unique hash key for this template name."""
154 hash = sha1(name.encode('utf-8'))
155 if filename is not None:
156 if isinstance(filename, unicode):
157 filename = filename.encode('utf-8')
158 hash.update('|' + filename)
159 return hash.hexdigest()
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200160
161 def get_source_checksum(self, source):
Armin Ronachera816bf42008-09-17 21:28:01 +0200162 """Returns a checksum for the source."""
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200163 return sha1(source.encode('utf-8')).hexdigest()
164
Armin Ronachera816bf42008-09-17 21:28:01 +0200165 def get_bucket(self, environment, name, filename, source):
166 """Return a cache bucket for the given template. All arguments are
167 mandatory but filename may be `None`.
168 """
169 key = self.get_cache_key(name, filename)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200170 checksum = self.get_source_checksum(source)
Armin Ronachera816bf42008-09-17 21:28:01 +0200171 bucket = Bucket(environment, key, checksum)
172 self.load_bytecode(bucket)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200173 return bucket
174
Armin Ronachera816bf42008-09-17 21:28:01 +0200175 def set_bucket(self, bucket):
176 """Put the bucket into the cache."""
177 self.dump_bytecode(bucket)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200178
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200179
Armin Ronachera816bf42008-09-17 21:28:01 +0200180class FileSystemBytecodeCache(BytecodeCache):
181 """A bytecode cache that stores bytecode on the filesystem. It accepts
182 two arguments: The directory where the cache items are stored and a
183 pattern string that is used to build the filename.
184
185 If no directory is specified the system temporary items folder is used.
186
187 The pattern can be used to have multiple separate caches operate on the
188 same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
189 is replaced with the cache key.
190
191 >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200192
193 This bytecode cache supports clearing of the cache using the clear method.
Armin Ronachera816bf42008-09-17 21:28:01 +0200194 """
195
196 def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
197 if directory is None:
198 directory = tempfile.gettempdir()
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200199 self.directory = directory
200 self.pattern = pattern
201
202 def _get_cache_filename(self, bucket):
203 return path.join(self.directory, self.pattern % bucket.key)
204
Armin Ronachera816bf42008-09-17 21:28:01 +0200205 def load_bytecode(self, bucket):
Armin Ronacherccae0552008-10-05 23:08:58 +0200206 f = open_if_exists(self._get_cache_filename(bucket), 'rb')
207 if f is not None:
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200208 try:
Armin Ronachera816bf42008-09-17 21:28:01 +0200209 bucket.load_bytecode(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200210 finally:
211 f.close()
212
Armin Ronachera816bf42008-09-17 21:28:01 +0200213 def dump_bytecode(self, bucket):
Armin Ronacher0faa8612010-02-09 15:04:51 +0100214 f = open(self._get_cache_filename(bucket), 'wb')
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200215 try:
Armin Ronachera816bf42008-09-17 21:28:01 +0200216 bucket.write_bytecode(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200217 finally:
218 f.close()
Armin Ronachera816bf42008-09-17 21:28:01 +0200219
220 def clear(self):
Max Ischenko03f88232008-09-18 16:23:33 +0200221 # imported lazily here because google app-engine doesn't support
222 # write access on the file system and the function does not exist
223 # normally.
224 from os import remove
Armin Ronacher2e46a5c2008-09-17 22:25:04 +0200225 files = fnmatch.filter(listdir(self.directory), self.pattern % '*')
226 for filename in files:
Armin Ronachera816bf42008-09-17 21:28:01 +0200227 try:
228 remove(path.join(self.directory, filename))
229 except OSError:
230 pass
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200231
232
233class MemcachedBytecodeCache(BytecodeCache):
234 """This class implements a bytecode cache that uses a memcache cache for
235 storing the information. It does not enforce a specific memcache library
236 (tummy's memcache or cmemcache) but will accept any class that provides
237 the minimal interface required.
238
239 Libraries compatible with this class:
240
241 - `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
242 - `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
243 - `cmemcache <http://gijsbert.org/cmemcache/>`_
244
245 (Unfortunately the django cache interface is not compatible because it
246 does not support storing binary data, only unicode. You can however pass
Georg Brandl3e497b72008-09-19 09:55:17 +0000247 the underlying cache client to the bytecode cache which is available
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200248 as `django.core.cache.cache._client`.)
249
250 The minimal interface for the client passed to the constructor is this:
251
252 .. class:: MinimalClientInterface
253
254 .. method:: set(key, value[, timeout])
255
256 Stores the bytecode in the cache. `value` is a string and
257 `timeout` the timeout of the key. If timeout is not provided
258 a default timeout or no timeout should be assumed, if it's
259 provided it's an integer with the number of seconds the cache
260 item should exist.
261
262 .. method:: get(key)
263
264 Returns the value for the cache key. If the item does not
265 exist in the cache the return value must be `None`.
266
267 The other arguments to the constructor are the prefix for all keys that
268 is added before the actual cache key and the timeout for the bytecode in
269 the cache system. We recommend a high (or no) timeout.
270
271 This bytecode cache does not support clearing of used items in the cache.
272 The clear method is a no-operation function.
273 """
274
275 def __init__(self, client, prefix='jinja2/bytecode/', timeout=None):
276 self.client = client
277 self.prefix = prefix
278 self.timeout = timeout
279
280 def load_bytecode(self, bucket):
281 code = self.client.get(self.prefix + bucket.key)
282 if code is not None:
283 bucket.bytecode_from_string(code)
284
285 def dump_bytecode(self, bucket):
286 args = (self.prefix + bucket.key, bucket.bytecode_to_string())
287 if self.timeout is not None:
288 args += (self.timeout,)
289 self.client.set(*args)