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

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

## 40 statements 34 run 6 missing 0 excluded 1 partial

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'''

2 The CacheFile object exposes a get, wrap and close interface to handlers.

3

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.

7

8 Each type of store has a separate CacheFile. (MemoryCacheFile, DiskCacheFile,

9 etc.) The parent CacheFile implements the no-caching behaviour.

10

11 See gramex.handlers.BaseHandler for examples on how to use these objects.

12'''

13 from six.moves import cPickle

14 from diskcache import Cache as DiskCache

15 from .ttlcache import TTLCache as MemoryCache

16 from gramex.config import app_log

17 from gramex.http import OK, NOT_MODIFIED

18

19# HTTP Headers that should not be cached

20 ignore_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}

30

31

32 def 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

40

41

42 class CacheFile(object):

43

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

50

51 def get(self):

52 return None

53

54 def wrap(self, handler):

55 return handler

56

57

58 class MemoryCacheFile(CacheFile):

59 def get(self):

60 result = self.store.get(self.key)

61 return None if result is None else cPickle.loads(result)

62

63 def wrap(self, handler):

64 self._finish = handler.finish

65

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)

71

72 # Call the original finish

73 self._finish(chunk)

74

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 )

87

88 handler.finish = finish

89

90

91 class DiskCacheFile(MemoryCacheFile):

92 '''Identical interface to MemoryCacheFile'''

93 pass