blob: 1a0e7ec7c984a5f1e13116f619937f9620ca859f [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 Ronachera816bf42008-09-17 21:28:01 +020060 checksum = pickle.load_bytecode(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020061 if self.checksum != checksum:
62 self.reset()
63 return
Armin Ronachera816bf42008-09-17 21:28:01 +020064 # now load_bytecode the code. Because marshal is not able to load_bytecode
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020065 # from arbitrary streams we have to work around that
66 if isinstance(f, file):
Armin Ronachera816bf42008-09-17 21:28:01 +020067 self.code = marshal.load_bytecode(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 Ronachera816bf42008-09-17 21:28:01 +020076 pickle.write_bytecode(self.checksum, f, 2)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020077 if isinstance(f, file):
Armin Ronachera816bf42008-09-17 21:28:01 +020078 marshal.write_bytecode(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')
182 """
183
184 def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
185 if directory is None:
186 directory = tempfile.gettempdir()
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200187 self.directory = directory
188 self.pattern = pattern
189
190 def _get_cache_filename(self, bucket):
191 return path.join(self.directory, self.pattern % bucket.key)
192
Armin Ronachera816bf42008-09-17 21:28:01 +0200193 def load_bytecode(self, bucket):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200194 filename = self._get_cache_filename(bucket)
195 if path.exists(filename):
196 f = file(filename, 'rb')
197 try:
Armin Ronachera816bf42008-09-17 21:28:01 +0200198 bucket.load_bytecode(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200199 finally:
200 f.close()
201
Armin Ronachera816bf42008-09-17 21:28:01 +0200202 def dump_bytecode(self, bucket):
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200203 filename = self._get_cache_filename(bucket)
204 f = file(filename, 'wb')
205 try:
Armin Ronachera816bf42008-09-17 21:28:01 +0200206 bucket.write_bytecode(f)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200207 finally:
208 f.close()
Armin Ronachera816bf42008-09-17 21:28:01 +0200209
210 def clear(self):
Max Ischenko03f88232008-09-18 16:23:33 +0200211 # imported lazily here because google app-engine doesn't support
212 # write access on the file system and the function does not exist
213 # normally.
214 from os import remove
Armin Ronacher2e46a5c2008-09-17 22:25:04 +0200215 files = fnmatch.filter(listdir(self.directory), self.pattern % '*')
216 for filename in files:
Armin Ronachera816bf42008-09-17 21:28:01 +0200217 try:
218 remove(path.join(self.directory, filename))
219 except OSError:
220 pass