blob: de6d2b402a3a35bfcecf1f0d05e8fb3cca2fdf86 [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
14 :copyright: Copyright 2008 by Armin Ronacher.
15 :license: BSD.
16"""
Max Ischenko03f88232008-09-18 16:23:33 +020017from os import path, listdir
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020018import marshal
Armin Ronachera816bf42008-09-17 21:28:01 +020019import tempfile
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020020import cPickle as pickle
Armin Ronachera816bf42008-09-17 21:28:01 +020021import fnmatch
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020022from cStringIO import StringIO
23try:
24 from hashlib import sha1
25except ImportError:
26 from sha import new as sha1
27
28
29bc_version = 1
30bc_magic = 'j2' + pickle.dumps(bc_version, 2)
31
32
33class Bucket(object):
Armin Ronachera816bf42008-09-17 21:28:01 +020034 """Buckets are used to store the bytecode for one template. It's created
35 and initialized by the bytecode cache and passed to the loading functions.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020036
Armin Ronachera816bf42008-09-17 21:28:01 +020037 The buckets get an internal checksum from the cache assigned and use this
38 to automatically reject outdated cache material. Individual bytecode
39 cache subclasses don't have to care about cache invalidation.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020040 """
41
Armin Ronachera816bf42008-09-17 21:28:01 +020042 def __init__(self, environment, key, checksum):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020043 self.environment = environment
44 self.key = key
45 self.checksum = checksum
46 self.reset()
47
48 def reset(self):
Armin Ronachera816bf42008-09-17 21:28:01 +020049 """Resets the bucket (unloads the bytecode)."""
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020050 self.code = None
51
Armin Ronachera816bf42008-09-17 21:28:01 +020052 def load_bytecode(self, f):
53 """Loads bytecode from a file or file like object."""
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020054 # make sure the magic header is correct
55 magic = f.read(len(bc_magic))
56 if magic != bc_magic:
57 self.reset()
58 return
59 # the source code of the file changed, we need to reload
Armin Ronacheraa1d17d2008-09-18 18:09:06 +020060 checksum = pickle.load(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020061 if self.checksum != checksum:
62 self.reset()
63 return
Armin Ronacheraa1d17d2008-09-18 18:09:06 +020064 # now load the code. Because marshal is not able to load
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020065 # from arbitrary streams we have to work around that
66 if isinstance(f, file):
Armin Ronacheraa1d17d2008-09-18 18:09:06 +020067 self.code = marshal.load(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020068 else:
69 self.code = marshal.loads(f.read())
70
Armin Ronachera816bf42008-09-17 21:28:01 +020071 def write_bytecode(self, f):
72 """Dump the bytecode into the file or file like object passed."""
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020073 if self.code is None:
74 raise TypeError('can\'t write empty bucket')
75 f.write(bc_magic)
Armin Ronacheraa1d17d2008-09-18 18:09:06 +020076 pickle.dump(self.checksum, f, 2)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020077 if isinstance(f, file):
Armin Ronacheraa1d17d2008-09-18 18:09:06 +020078 marshal.dump(self.code, f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020079 else:
80 f.write(marshal.dumps(self.code))
81
Armin Ronachera816bf42008-09-17 21:28:01 +020082 def bytecode_from_string(self, string):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020083 """Load bytecode from a string."""
Armin Ronachera816bf42008-09-17 21:28:01 +020084 self.load_bytecode(StringIO(string))
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020085
Armin Ronachera816bf42008-09-17 21:28:01 +020086 def bytecode_to_string(self):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020087 """Return the bytecode as string."""
88 out = StringIO()
Armin Ronachera816bf42008-09-17 21:28:01 +020089 self.write_bytecode(out)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020090 return out.getvalue()
91
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020092
93class BytecodeCache(object):
94 """To implement your own bytecode cache you have to subclass this class
Armin Ronachera816bf42008-09-17 21:28:01 +020095 and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
96 these methods are passed a :class:`~jinja2.bccache.Bucket`.
97
98 A very basic bytecode cache that saves the bytecode on the file system::
99
100 from os import path
101
102 class MyCache(BytecodeCache):
103
104 def __init__(self, directory):
105 self.directory = directory
106
107 def load_bytecode(self, bucket):
108 filename = path.join(self.directory, bucket.key)
109 if path.exists(filename):
110 with file(filename, 'rb') as f:
111 bucket.load_bytecode(f)
112
113 def dump_bytecode(self, bucket):
114 filename = path.join(self.directory, bucket.key)
115 with file(filename, 'wb') as f:
116 bucket.write_bytecode(f)
117
118 A more advanced version of a filesystem based bytecode cache is part of
119 Jinja2.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200120 """
121
Armin Ronachera816bf42008-09-17 21:28:01 +0200122 def load_bytecode(self, bucket):
123 """Subclasses have to override this method to load bytecode into a
124 bucket. If they are not able to find code in the cache for the
125 bucket, it must not do anything.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200126 """
127 raise NotImplementedError()
128
Armin Ronachera816bf42008-09-17 21:28:01 +0200129 def dump_bytecode(self, bucket):
130 """Subclasses have to override this method to write the bytecode
131 from a bucket back to the cache. If it unable to do so it must not
132 fail silently but raise an exception.
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200133 """
134 raise NotImplementedError()
135
Armin Ronachera816bf42008-09-17 21:28:01 +0200136 def clear(self):
137 """Clears the cache. This method is not used by Jinja2 but should be
138 implemented to allow applications to clear the bytecode cache used
139 by a particular environment.
140 """
141
142 def get_cache_key(self, name, filename=None):
143 """Returns the unique hash key for this template name."""
144 hash = sha1(name.encode('utf-8'))
145 if filename is not None:
146 if isinstance(filename, unicode):
147 filename = filename.encode('utf-8')
148 hash.update('|' + filename)
149 return hash.hexdigest()
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200150
151 def get_source_checksum(self, source):
Armin Ronachera816bf42008-09-17 21:28:01 +0200152 """Returns a checksum for the source."""
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200153 return sha1(source.encode('utf-8')).hexdigest()
154
Armin Ronachera816bf42008-09-17 21:28:01 +0200155 def get_bucket(self, environment, name, filename, source):
156 """Return a cache bucket for the given template. All arguments are
157 mandatory but filename may be `None`.
158 """
159 key = self.get_cache_key(name, filename)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200160 checksum = self.get_source_checksum(source)
Armin Ronachera816bf42008-09-17 21:28:01 +0200161 bucket = Bucket(environment, key, checksum)
162 self.load_bytecode(bucket)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200163 return bucket
164
Armin Ronachera816bf42008-09-17 21:28:01 +0200165 def set_bucket(self, bucket):
166 """Put the bucket into the cache."""
167 self.dump_bytecode(bucket)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200168
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200169
Armin Ronachera816bf42008-09-17 21:28:01 +0200170class FileSystemBytecodeCache(BytecodeCache):
171 """A bytecode cache that stores bytecode on the filesystem. It accepts
172 two arguments: The directory where the cache items are stored and a
173 pattern string that is used to build the filename.
174
175 If no directory is specified the system temporary items folder is used.
176
177 The pattern can be used to have multiple separate caches operate on the
178 same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
179 is replaced with the cache key.
180
181 >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200182
183 This bytecode cache supports clearing of the cache using the clear method.
Armin Ronachera816bf42008-09-17 21:28:01 +0200184 """
185
186 def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
187 if directory is None:
188 directory = tempfile.gettempdir()
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200189 self.directory = directory
190 self.pattern = pattern
191
192 def _get_cache_filename(self, bucket):
193 return path.join(self.directory, self.pattern % bucket.key)
194
Armin Ronachera816bf42008-09-17 21:28:01 +0200195 def load_bytecode(self, bucket):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200196 filename = self._get_cache_filename(bucket)
197 if path.exists(filename):
198 f = file(filename, 'rb')
199 try:
Armin Ronachera816bf42008-09-17 21:28:01 +0200200 bucket.load_bytecode(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200201 finally:
202 f.close()
203
Armin Ronachera816bf42008-09-17 21:28:01 +0200204 def dump_bytecode(self, bucket):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200205 filename = self._get_cache_filename(bucket)
206 f = file(filename, 'wb')
207 try:
Armin Ronachera816bf42008-09-17 21:28:01 +0200208 bucket.write_bytecode(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200209 finally:
210 f.close()
Armin Ronachera816bf42008-09-17 21:28:01 +0200211
212 def clear(self):
Max Ischenko03f88232008-09-18 16:23:33 +0200213 # imported lazily here because google app-engine doesn't support
214 # write access on the file system and the function does not exist
215 # normally.
216 from os import remove
Armin Ronacher2e46a5c2008-09-17 22:25:04 +0200217 files = fnmatch.filter(listdir(self.directory), self.pattern % '*')
218 for filename in files:
Armin Ronachera816bf42008-09-17 21:28:01 +0200219 try:
220 remove(path.join(self.directory, filename))
221 except OSError:
222 pass
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200223
224
225class MemcachedBytecodeCache(BytecodeCache):
226 """This class implements a bytecode cache that uses a memcache cache for
227 storing the information. It does not enforce a specific memcache library
228 (tummy's memcache or cmemcache) but will accept any class that provides
229 the minimal interface required.
230
231 Libraries compatible with this class:
232
233 - `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
234 - `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
235 - `cmemcache <http://gijsbert.org/cmemcache/>`_
236
237 (Unfortunately the django cache interface is not compatible because it
238 does not support storing binary data, only unicode. You can however pass
239 the underlaying cache client to the bytecode cache which is available
240 as `django.core.cache.cache._client`.)
241
242 The minimal interface for the client passed to the constructor is this:
243
244 .. class:: MinimalClientInterface
245
246 .. method:: set(key, value[, timeout])
247
248 Stores the bytecode in the cache. `value` is a string and
249 `timeout` the timeout of the key. If timeout is not provided
250 a default timeout or no timeout should be assumed, if it's
251 provided it's an integer with the number of seconds the cache
252 item should exist.
253
254 .. method:: get(key)
255
256 Returns the value for the cache key. If the item does not
257 exist in the cache the return value must be `None`.
258
259 The other arguments to the constructor are the prefix for all keys that
260 is added before the actual cache key and the timeout for the bytecode in
261 the cache system. We recommend a high (or no) timeout.
262
263 This bytecode cache does not support clearing of used items in the cache.
264 The clear method is a no-operation function.
265 """
266
267 def __init__(self, client, prefix='jinja2/bytecode/', timeout=None):
268 self.client = client
269 self.prefix = prefix
270 self.timeout = timeout
271
272 def load_bytecode(self, bucket):
273 code = self.client.get(self.prefix + bucket.key)
274 if code is not None:
275 bucket.bytecode_from_string(code)
276
277 def dump_bytecode(self, bucket):
278 args = (self.prefix + bucket.key, bucket.bytecode_to_string())
279 if self.timeout is not None:
280 args += (self.timeout,)
281 self.client.set(*args)