mirror of https://github.com/opencv/opencv.git
Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
11 KiB
311 lines
11 KiB
# -*- 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: (c) 2010 by the Jinja Team. |
|
:license: BSD. |
|
""" |
|
from os import path, listdir |
|
import sys |
|
import marshal |
|
import tempfile |
|
import fnmatch |
|
from hashlib import sha1 |
|
from jinja2.utils import open_if_exists |
|
from jinja2._compat import BytesIO, pickle, PY2, text_type |
|
|
|
|
|
# marshal works better on 3.x, one hack less required |
|
if not PY2: |
|
marshal_dump = marshal.dump |
|
marshal_load = marshal.load |
|
else: |
|
|
|
def marshal_dump(code, f): |
|
if isinstance(f, file): |
|
marshal.dump(code, f) |
|
else: |
|
f.write(marshal.dumps(code)) |
|
|
|
def marshal_load(f): |
|
if isinstance(f, file): |
|
return marshal.load(f) |
|
return marshal.loads(f.read()) |
|
|
|
|
|
bc_version = 2 |
|
|
|
# magic version used to only change with new jinja versions. With 2.6 |
|
# we change this to also take Python version changes into account. The |
|
# reason for this is that Python tends to segfault if fed earlier bytecode |
|
# versions because someone thought it would be a good idea to reuse opcodes |
|
# or make Python incompatible with earlier versions. |
|
bc_magic = 'j2'.encode('ascii') + \ |
|
pickle.dumps(bc_version, 2) + \ |
|
pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1]) |
|
|
|
|
|
class Bucket(object): |
|
"""Buckets are used to store the bytecode for one template. It's created |
|
and initialized by the bytecode cache and passed to the loading functions. |
|
|
|
The buckets get an internal checksum from the cache assigned and use this |
|
to automatically reject outdated cache material. Individual bytecode |
|
cache subclasses don't have to care about cache invalidation. |
|
""" |
|
|
|
def __init__(self, environment, key, checksum): |
|
self.environment = environment |
|
self.key = key |
|
self.checksum = checksum |
|
self.reset() |
|
|
|
def reset(self): |
|
"""Resets the bucket (unloads the bytecode).""" |
|
self.code = None |
|
|
|
def load_bytecode(self, f): |
|
"""Loads bytecode from a file or file like object.""" |
|
# 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 |
|
self.code = marshal_load(f) |
|
|
|
def write_bytecode(self, f): |
|
"""Dump the bytecode into the file or file like object passed.""" |
|
if self.code is None: |
|
raise TypeError('can\'t write empty bucket') |
|
f.write(bc_magic) |
|
pickle.dump(self.checksum, f, 2) |
|
marshal_dump(self.code, f) |
|
|
|
def bytecode_from_string(self, string): |
|
"""Load bytecode from a string.""" |
|
self.load_bytecode(BytesIO(string)) |
|
|
|
def bytecode_to_string(self): |
|
"""Return the bytecode as string.""" |
|
out = BytesIO() |
|
self.write_bytecode(out) |
|
return out.getvalue() |
|
|
|
|
|
class BytecodeCache(object): |
|
"""To implement your own bytecode cache you have to subclass this class |
|
and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of |
|
these methods are passed a :class:`~jinja2.bccache.Bucket`. |
|
|
|
A very basic bytecode cache that saves the bytecode on the file system:: |
|
|
|
from os import path |
|
|
|
class MyCache(BytecodeCache): |
|
|
|
def __init__(self, directory): |
|
self.directory = directory |
|
|
|
def load_bytecode(self, bucket): |
|
filename = path.join(self.directory, bucket.key) |
|
if path.exists(filename): |
|
with open(filename, 'rb') as f: |
|
bucket.load_bytecode(f) |
|
|
|
def dump_bytecode(self, bucket): |
|
filename = path.join(self.directory, bucket.key) |
|
with open(filename, 'wb') as f: |
|
bucket.write_bytecode(f) |
|
|
|
A more advanced version of a filesystem based bytecode cache is part of |
|
Jinja2. |
|
""" |
|
|
|
def load_bytecode(self, bucket): |
|
"""Subclasses have to override this method to load bytecode into a |
|
bucket. If they are not able to find code in the cache for the |
|
bucket, it must not do anything. |
|
""" |
|
raise NotImplementedError() |
|
|
|
def dump_bytecode(self, bucket): |
|
"""Subclasses have to override this method to write the bytecode |
|
from a bucket back to the cache. If it unable to do so it must not |
|
fail silently but raise an exception. |
|
""" |
|
raise NotImplementedError() |
|
|
|
def clear(self): |
|
"""Clears the cache. This method is not used by Jinja2 but should be |
|
implemented to allow applications to clear the bytecode cache used |
|
by a particular environment. |
|
""" |
|
|
|
def get_cache_key(self, name, filename=None): |
|
"""Returns the unique hash key for this template name.""" |
|
hash = sha1(name.encode('utf-8')) |
|
if filename is not None: |
|
filename = '|' + filename |
|
if isinstance(filename, text_type): |
|
filename = filename.encode('utf-8') |
|
hash.update(filename) |
|
return hash.hexdigest() |
|
|
|
def get_source_checksum(self, source): |
|
"""Returns a checksum for the source.""" |
|
return sha1(source.encode('utf-8')).hexdigest() |
|
|
|
def get_bucket(self, environment, name, filename, source): |
|
"""Return a cache bucket for the given template. All arguments are |
|
mandatory but filename may be `None`. |
|
""" |
|
key = self.get_cache_key(name, filename) |
|
checksum = self.get_source_checksum(source) |
|
bucket = Bucket(environment, key, checksum) |
|
self.load_bytecode(bucket) |
|
return bucket |
|
|
|
def set_bucket(self, bucket): |
|
"""Put the bucket into the cache.""" |
|
self.dump_bytecode(bucket) |
|
|
|
|
|
class FileSystemBytecodeCache(BytecodeCache): |
|
"""A bytecode cache that stores bytecode on the filesystem. It accepts |
|
two arguments: The directory where the cache items are stored and a |
|
pattern string that is used to build the filename. |
|
|
|
If no directory is specified the system temporary items folder is used. |
|
|
|
The pattern can be used to have multiple separate caches operate on the |
|
same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s`` |
|
is replaced with the cache key. |
|
|
|
>>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache') |
|
|
|
This bytecode cache supports clearing of the cache using the clear method. |
|
""" |
|
|
|
def __init__(self, directory=None, pattern='__jinja2_%s.cache'): |
|
if directory is None: |
|
directory = tempfile.gettempdir() |
|
self.directory = directory |
|
self.pattern = pattern |
|
|
|
def _get_cache_filename(self, bucket): |
|
return path.join(self.directory, self.pattern % bucket.key) |
|
|
|
def load_bytecode(self, bucket): |
|
f = open_if_exists(self._get_cache_filename(bucket), 'rb') |
|
if f is not None: |
|
try: |
|
bucket.load_bytecode(f) |
|
finally: |
|
f.close() |
|
|
|
def dump_bytecode(self, bucket): |
|
f = open(self._get_cache_filename(bucket), 'wb') |
|
try: |
|
bucket.write_bytecode(f) |
|
finally: |
|
f.close() |
|
|
|
def clear(self): |
|
# imported lazily here because google app-engine doesn't support |
|
# write access on the file system and the function does not exist |
|
# normally. |
|
from os import remove |
|
files = fnmatch.filter(listdir(self.directory), self.pattern % '*') |
|
for filename in files: |
|
try: |
|
remove(path.join(self.directory, filename)) |
|
except OSError: |
|
pass |
|
|
|
|
|
class MemcachedBytecodeCache(BytecodeCache): |
|
"""This class implements a bytecode cache that uses a memcache cache for |
|
storing the information. It does not enforce a specific memcache library |
|
(tummy's memcache or cmemcache) but will accept any class that provides |
|
the minimal interface required. |
|
|
|
Libraries compatible with this class: |
|
|
|
- `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache |
|
- `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_ |
|
- `cmemcache <http://gijsbert.org/cmemcache/>`_ |
|
|
|
(Unfortunately the django cache interface is not compatible because it |
|
does not support storing binary data, only unicode. You can however pass |
|
the underlying cache client to the bytecode cache which is available |
|
as `django.core.cache.cache._client`.) |
|
|
|
The minimal interface for the client passed to the constructor is this: |
|
|
|
.. class:: MinimalClientInterface |
|
|
|
.. method:: set(key, value[, timeout]) |
|
|
|
Stores the bytecode in the cache. `value` is a string and |
|
`timeout` the timeout of the key. If timeout is not provided |
|
a default timeout or no timeout should be assumed, if it's |
|
provided it's an integer with the number of seconds the cache |
|
item should exist. |
|
|
|
.. method:: get(key) |
|
|
|
Returns the value for the cache key. If the item does not |
|
exist in the cache the return value must be `None`. |
|
|
|
The other arguments to the constructor are the prefix for all keys that |
|
is added before the actual cache key and the timeout for the bytecode in |
|
the cache system. We recommend a high (or no) timeout. |
|
|
|
This bytecode cache does not support clearing of used items in the cache. |
|
The clear method is a no-operation function. |
|
|
|
.. versionadded:: 2.7 |
|
Added support for ignoring memcache errors through the |
|
`ignore_memcache_errors` parameter. |
|
""" |
|
|
|
def __init__(self, client, prefix='jinja2/bytecode/', timeout=None, |
|
ignore_memcache_errors=True): |
|
self.client = client |
|
self.prefix = prefix |
|
self.timeout = timeout |
|
self.ignore_memcache_errors = ignore_memcache_errors |
|
|
|
def load_bytecode(self, bucket): |
|
try: |
|
code = self.client.get(self.prefix + bucket.key) |
|
except Exception: |
|
if not self.ignore_memcache_errors: |
|
raise |
|
code = None |
|
if code is not None: |
|
bucket.bytecode_from_string(code) |
|
|
|
def dump_bytecode(self, bucket): |
|
args = (self.prefix + bucket.key, bucket.bytecode_to_string()) |
|
if self.timeout is not None: |
|
args += (self.timeout,) |
|
try: |
|
self.client.set(*args) |
|
except Exception: |
|
if not self.ignore_memcache_errors: |
|
raise
|
|
|