Coverage for gramex\services\urlcache.py : 81%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1'''
2The CacheFile object exposes a get, wrap and close interface to handlers.
4- ``.get()`` reads all data against the key
5- ``.wrap(handler)`` is used to wrap the ``.write()`` method to append into a
6 write queue, and the ``.on_finish()`` method to save the result.
8Each type of store has a separate CacheFile. (MemoryCacheFile, DiskCacheFile,
9etc.) The parent CacheFile implements the no-caching behaviour.
11See gramex.handlers.BaseHandler for examples on how to use these objects.
12'''
13from six.moves import cPickle
14from diskcache import Cache as DiskCache
15from .ttlcache import TTLCache as MemoryCache
16from gramex.config import app_log
17from gramex.http import OK, NOT_MODIFIED
19# HTTP Headers that should not be cached
20ignore_headers = {
21 # Do not cache headers referenced anywhere in tornado.http1connection
22 'Content-Encoding', 'Vary', 'Transfer-Encoding', 'Expect',
23 'Keep-Alive', 'Connection', 'X-Consumed-Content-Encoding',
24 # Do not cache things that SHOULD or WILL be recomputed anyway
25 'Date', # This is the current date, not the Last-Modified date
26 'Server', # Always show Gramex/version
27 'Etag', # Automatically added by Tornado
28 'Content-Length', # Automatically added by Tornado
29}
32def get_cachefile(store):
33 if isinstance(store, MemoryCache): 33 ↛ 35line 33 didn't jump to line 35, because the condition on line 33 was never false
34 return MemoryCacheFile
35 elif isinstance(store, DiskCache):
36 return DiskCacheFile
37 else:
38 app_log.warning('cache: ignoring unknown store %s', store)
39 return CacheFile
42class CacheFile(object):
44 def __init__(self, key, store, handler, expire=None, statuses=None):
45 self.key = key
46 self.store = store
47 self.handler = handler
48 self.expire = expire
49 self.statuses = statuses
51 def get(self):
52 return None
54 def wrap(self, handler):
55 return handler
58class MemoryCacheFile(CacheFile):
59 def get(self):
60 result = self.store.get(self.key)
61 return None if result is None else cPickle.loads(result)
63 def wrap(self, handler):
64 self._finish = handler.finish
66 def finish(chunk=None):
67 # Save the headers and body originally written
68 headers = [[name, value] for name, value in handler._headers.get_all()
69 if name not in ignore_headers]
70 body = b''.join(handler._write_buffer)
72 # Call the original finish
73 self._finish(chunk)
75 # Cache headers and body (only for allowed HTTP responses)
76 status = handler.get_status()
77 if status in self.statuses:
78 self.store.set(
79 key=self.key,
80 value=cPickle.dumps({
81 'status': OK if status == NOT_MODIFIED else status,
82 'headers': headers,
83 'body': body,
84 }, cPickle.HIGHEST_PROTOCOL),
85 expire=self.expire,
86 )
88 handler.finish = finish
91class DiskCacheFile(MemoryCacheFile):
92 '''Identical interface to MemoryCacheFile'''
93 pass