blob: 50532cdf2b30b1d225f516bd2b3e9a4b99941e50 [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"""
17from os import path
18import marshal
19import cPickle as pickle
20from cStringIO import StringIO
21try:
22 from hashlib import sha1
23except ImportError:
24 from sha import new as sha1
25
26
27bc_version = 1
28bc_magic = 'j2' + pickle.dumps(bc_version, 2)
29
30
31class Bucket(object):
32 """Buckets are used to store the bytecode for one template. It's
33 initialized by the bytecode cache with the checksum for the code
34 as well as the unique key.
35
36 The bucket then provides method to load the bytecode from file(-like)
37 objects and strings or dump it again.
38 """
39
40 def __init__(self, cache, environment, key, checksum):
41 self._cache = cache
42 self.environment = environment
43 self.key = key
44 self.checksum = checksum
45 self.reset()
46
47 def reset(self):
48 """Resets the bucket (unloads the code)."""
49 self.code = None
50
51 def load(self, f):
52 """Loads bytecode from a f."""
53 # make sure the magic header is correct
54 magic = f.read(len(bc_magic))
55 if magic != bc_magic:
56 self.reset()
57 return
58 # the source code of the file changed, we need to reload
59 checksum = pickle.load(f)
60 if self.checksum != checksum:
61 self.reset()
62 return
63 # now load the code. Because marshal is not able to load
64 # from arbitrary streams we have to work around that
65 if isinstance(f, file):
66 self.code = marshal.load(f)
67 else:
68 self.code = marshal.loads(f.read())
69
70 def dump(self, f):
71 """Dump the bytecode into f."""
72 if self.code is None:
73 raise TypeError('can\'t write empty bucket')
74 f.write(bc_magic)
75 pickle.dump(self.checksum, f, 2)
76 if isinstance(f, file):
77 marshal.dump(self.code, f)
78 else:
79 f.write(marshal.dumps(self.code))
80
81 def loads(self, string):
82 """Load bytecode from a string."""
83 self.load(StringIO(string))
84
85 def dumps(self):
86 """Return the bytecode as string."""
87 out = StringIO()
88 self.dump(out)
89 return out.getvalue()
90
91 def write_back(self):
92 """Write the bucket back to the cache."""
93 self._cache.dump_bucket(self)
94
95
96class BytecodeCache(object):
97 """To implement your own bytecode cache you have to subclass this class
98 and override :meth:`load_bucket` and :meth:`dump_bucket`. Both of these
99 methods are passed a :class:`Bucket` that they have to load or dump.
100 """
101
102 def load_bucket(self, bucket):
103 """Subclasses have to override this method to load bytecode
104 into a bucket.
105 """
106 raise NotImplementedError()
107
108 def dump_bucket(self, bucket):
109 """Subclasses have to override this method to write the
110 bytecode from a bucket back to the cache.
111 """
112 raise NotImplementedError()
113
114 def get_cache_key(self, name):
115 """Return the unique hash key for this template name."""
116 return sha1(name.encode('utf-8')).hexdigest()
117
118 def get_source_checksum(self, source):
119 """Return a checksum for the source."""
120 return sha1(source.encode('utf-8')).hexdigest()
121
122 def get_bucket(self, environment, name, source):
123 """Return a cache bucket."""
124 key = self.get_cache_key(name)
125 checksum = self.get_source_checksum(source)
126 bucket = Bucket(self, environment, key, checksum)
127 self.load_bucket(bucket)
128 return bucket
129
130
131class FileSystemCache(BytecodeCache):
132 """A bytecode cache that stores bytecode on the filesystem."""
133
134 def __init__(self, directory, pattern='%s.jbc'):
135 self.directory = directory
136 self.pattern = pattern
137
138 def _get_cache_filename(self, bucket):
139 return path.join(self.directory, self.pattern % bucket.key)
140
141 def load_bucket(self, bucket):
142 filename = self._get_cache_filename(bucket)
143 if path.exists(filename):
144 f = file(filename, 'rb')
145 try:
146 bucket.load(f)
147 finally:
148 f.close()
149
150 def dump_bucket(self, bucket):
151 filename = self._get_cache_filename(bucket)
152 f = file(filename, 'wb')
153 try:
154 bucket.dump(f)
155 finally:
156 f.close()