Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added experimental and undocumented bytecode cache support
--HG-- branch : trunk
- Loading branch information
Showing
4 changed files
with
185 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
jinja2.bccache | ||
~~~~~~~~~~~~~~ | ||
This module implements the bytecode cache system Jinja is optionally | ||
using. This is useful if you have very complex template situations and | ||
the compiliation of all those templates slow down your application too | ||
much. | ||
Situations where this is useful are often forking web applications that | ||
are initialized on the first request. | ||
:copyright: Copyright 2008 by Armin Ronacher. | ||
:license: BSD. | ||
""" | ||
from os import path | ||
import marshal | ||
import cPickle as pickle | ||
from cStringIO import StringIO | ||
try: | ||
from hashlib import sha1 | ||
except ImportError: | ||
from sha import new as sha1 | ||
|
||
|
||
bc_version = 1 | ||
bc_magic = 'j2' + pickle.dumps(bc_version, 2) | ||
|
||
|
||
class Bucket(object): | ||
"""Buckets are used to store the bytecode for one template. It's | ||
initialized by the bytecode cache with the checksum for the code | ||
as well as the unique key. | ||
The bucket then provides method to load the bytecode from file(-like) | ||
objects and strings or dump it again. | ||
""" | ||
|
||
def __init__(self, cache, environment, key, checksum): | ||
self._cache = cache | ||
self.environment = environment | ||
self.key = key | ||
self.checksum = checksum | ||
self.reset() | ||
|
||
def reset(self): | ||
"""Resets the bucket (unloads the code).""" | ||
self.code = None | ||
|
||
def load(self, f): | ||
"""Loads bytecode from a f.""" | ||
# make sure the magic header is correct | ||
magic = f.read(len(bc_magic)) | ||
if magic != bc_magic: | ||
self.reset() | ||
return | ||
# the source code of the file changed, we need to reload | ||
checksum = pickle.load(f) | ||
if self.checksum != checksum: | ||
self.reset() | ||
return | ||
# now load the code. Because marshal is not able to load | ||
# from arbitrary streams we have to work around that | ||
if isinstance(f, file): | ||
self.code = marshal.load(f) | ||
else: | ||
self.code = marshal.loads(f.read()) | ||
|
||
def dump(self, f): | ||
"""Dump the bytecode into f.""" | ||
if self.code is None: | ||
raise TypeError('can\'t write empty bucket') | ||
f.write(bc_magic) | ||
pickle.dump(self.checksum, f, 2) | ||
if isinstance(f, file): | ||
marshal.dump(self.code, f) | ||
else: | ||
f.write(marshal.dumps(self.code)) | ||
|
||
def loads(self, string): | ||
"""Load bytecode from a string.""" | ||
self.load(StringIO(string)) | ||
|
||
def dumps(self): | ||
"""Return the bytecode as string.""" | ||
out = StringIO() | ||
self.dump(out) | ||
return out.getvalue() | ||
|
||
def write_back(self): | ||
"""Write the bucket back to the cache.""" | ||
self._cache.dump_bucket(self) | ||
|
||
|
||
class BytecodeCache(object): | ||
"""To implement your own bytecode cache you have to subclass this class | ||
and override :meth:`load_bucket` and :meth:`dump_bucket`. Both of these | ||
methods are passed a :class:`Bucket` that they have to load or dump. | ||
""" | ||
|
||
def load_bucket(self, bucket): | ||
"""Subclasses have to override this method to load bytecode | ||
into a bucket. | ||
""" | ||
raise NotImplementedError() | ||
|
||
def dump_bucket(self, bucket): | ||
"""Subclasses have to override this method to write the | ||
bytecode from a bucket back to the cache. | ||
""" | ||
raise NotImplementedError() | ||
|
||
def get_cache_key(self, name): | ||
"""Return the unique hash key for this template name.""" | ||
return sha1(name.encode('utf-8')).hexdigest() | ||
|
||
def get_source_checksum(self, source): | ||
"""Return a checksum for the source.""" | ||
return sha1(source.encode('utf-8')).hexdigest() | ||
|
||
def get_bucket(self, environment, name, source): | ||
"""Return a cache bucket.""" | ||
key = self.get_cache_key(name) | ||
checksum = self.get_source_checksum(source) | ||
bucket = Bucket(self, environment, key, checksum) | ||
self.load_bucket(bucket) | ||
return bucket | ||
|
||
|
||
class FileSystemCache(BytecodeCache): | ||
"""A bytecode cache that stores bytecode on the filesystem.""" | ||
|
||
def __init__(self, directory, pattern='%s.jbc'): | ||
self.directory = directory | ||
self.pattern = pattern | ||
|
||
def _get_cache_filename(self, bucket): | ||
return path.join(self.directory, self.pattern % bucket.key) | ||
|
||
def load_bucket(self, bucket): | ||
filename = self._get_cache_filename(bucket) | ||
if path.exists(filename): | ||
f = file(filename, 'rb') | ||
try: | ||
bucket.load(f) | ||
finally: | ||
f.close() | ||
|
||
def dump_bucket(self, bucket): | ||
filename = self._get_cache_filename(bucket) | ||
f = file(filename, 'wb') | ||
try: | ||
bucket.dump(f) | ||
finally: | ||
f.close() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters