| 1 | # $Id: CacheRegion.py,v 1.3 2006/01/28 04:19:30 tavis_rudd Exp $ |
|---|
| 2 | ''' |
|---|
| 3 | Cache holder classes for Cheetah: |
|---|
| 4 | |
|---|
| 5 | Cache regions are defined using the #cache Cheetah directive. Each |
|---|
| 6 | cache region can be viewed as a dictionary (keyed by cacheRegionID) |
|---|
| 7 | handling at least one cache item (the default one). It's possible to add |
|---|
| 8 | cacheItems in a region by using the `varyBy` #cache directive parameter as |
|---|
| 9 | in the following example:: |
|---|
| 10 | #def getArticle |
|---|
| 11 | this is the article content. |
|---|
| 12 | #end def |
|---|
| 13 | |
|---|
| 14 | #cache varyBy=$getArticleID() |
|---|
| 15 | $getArticle($getArticleID()) |
|---|
| 16 | #end cache |
|---|
| 17 | |
|---|
| 18 | The code above will generate a CacheRegion and add new cacheItem for each value |
|---|
| 19 | of $getArticleID(). |
|---|
| 20 | ''' |
|---|
| 21 | |
|---|
| 22 | try: |
|---|
| 23 | from hashlib import md5 |
|---|
| 24 | except ImportError: |
|---|
| 25 | from md5 import md5 |
|---|
| 26 | |
|---|
| 27 | import time |
|---|
| 28 | import Cheetah.CacheStore |
|---|
| 29 | |
|---|
| 30 | class CacheItem(object): |
|---|
| 31 | ''' |
|---|
| 32 | A CacheItem is a container storing: |
|---|
| 33 | |
|---|
| 34 | - cacheID (string) |
|---|
| 35 | - refreshTime (timestamp or None) : last time the cache was refreshed |
|---|
| 36 | - data (string) : the content of the cache |
|---|
| 37 | ''' |
|---|
| 38 | |
|---|
| 39 | def __init__(self, cacheItemID, cacheStore): |
|---|
| 40 | self._cacheItemID = cacheItemID |
|---|
| 41 | self._cacheStore = cacheStore |
|---|
| 42 | self._refreshTime = None |
|---|
| 43 | self._expiryTime = 0 |
|---|
| 44 | |
|---|
| 45 | def hasExpired(self): |
|---|
| 46 | return (self._expiryTime and time.time() > self._expiryTime) |
|---|
| 47 | |
|---|
| 48 | def setExpiryTime(self, time): |
|---|
| 49 | self._expiryTime = time |
|---|
| 50 | |
|---|
| 51 | def getExpiryTime(self): |
|---|
| 52 | return self._expiryTime |
|---|
| 53 | |
|---|
| 54 | def setData(self, data): |
|---|
| 55 | self._refreshTime = time.time() |
|---|
| 56 | self._cacheStore.set(self._cacheItemID, data, self._expiryTime) |
|---|
| 57 | |
|---|
| 58 | def getRefreshTime(self): |
|---|
| 59 | return self._refreshTime |
|---|
| 60 | |
|---|
| 61 | def getData(self): |
|---|
| 62 | assert self._refreshTime |
|---|
| 63 | return self._cacheStore.get(self._cacheItemID) |
|---|
| 64 | |
|---|
| 65 | def renderOutput(self): |
|---|
| 66 | """Can be overridden to implement edge-caching""" |
|---|
| 67 | return self.getData() or "" |
|---|
| 68 | |
|---|
| 69 | def clear(self): |
|---|
| 70 | self._cacheStore.delete(self._cacheItemID) |
|---|
| 71 | self._refreshTime = None |
|---|
| 72 | |
|---|
| 73 | class _CacheDataStoreWrapper(object): |
|---|
| 74 | def __init__(self, dataStore, keyPrefix): |
|---|
| 75 | self._dataStore = dataStore |
|---|
| 76 | self._keyPrefix = keyPrefix |
|---|
| 77 | |
|---|
| 78 | def get(self, key): |
|---|
| 79 | return self._dataStore.get(self._keyPrefix+key) |
|---|
| 80 | |
|---|
| 81 | def delete(self, key): |
|---|
| 82 | self._dataStore.delete(self._keyPrefix+key) |
|---|
| 83 | |
|---|
| 84 | def set(self, key, val, time=0): |
|---|
| 85 | self._dataStore.set(self._keyPrefix+key, val, time=time) |
|---|
| 86 | |
|---|
| 87 | class CacheRegion(object): |
|---|
| 88 | ''' |
|---|
| 89 | A `CacheRegion` stores some `CacheItem` instances. |
|---|
| 90 | |
|---|
| 91 | This implementation stores the data in the memory of the current process. |
|---|
| 92 | If you need a more advanced data store, create a cacheStore class that works |
|---|
| 93 | with Cheetah's CacheStore protocol and provide it as the cacheStore argument |
|---|
| 94 | to __init__. For example you could use |
|---|
| 95 | Cheetah.CacheStore.MemcachedCacheStore, a wrapper around the Python |
|---|
| 96 | memcached API (http://www.danga.com/memcached). |
|---|
| 97 | ''' |
|---|
| 98 | _cacheItemClass = CacheItem |
|---|
| 99 | |
|---|
| 100 | def __init__(self, regionID, templateCacheIdPrefix='', cacheStore=None): |
|---|
| 101 | self._isNew = True |
|---|
| 102 | self._regionID = regionID |
|---|
| 103 | self._templateCacheIdPrefix = templateCacheIdPrefix |
|---|
| 104 | if not cacheStore: |
|---|
| 105 | cacheStore = Cheetah.CacheStore.MemoryCacheStore() |
|---|
| 106 | self._cacheStore = cacheStore |
|---|
| 107 | self._wrappedCacheDataStore = _CacheDataStoreWrapper( |
|---|
| 108 | cacheStore, keyPrefix=templateCacheIdPrefix+':'+regionID+':') |
|---|
| 109 | self._cacheItems = {} |
|---|
| 110 | |
|---|
| 111 | def isNew(self): |
|---|
| 112 | return self._isNew |
|---|
| 113 | |
|---|
| 114 | def clear(self): |
|---|
| 115 | " drop all the caches stored in this cache region " |
|---|
| 116 | for cacheItemId in self._cacheItems.keys(): |
|---|
| 117 | cacheItem = self._cacheItems[cacheItemId] |
|---|
| 118 | cacheItem.clear() |
|---|
| 119 | del self._cacheItems[cacheItemId] |
|---|
| 120 | |
|---|
| 121 | def getCacheItem(self, cacheItemID): |
|---|
| 122 | """ Lazy access to a cacheItem |
|---|
| 123 | |
|---|
| 124 | Try to find a cache in the stored caches. If it doesn't |
|---|
| 125 | exist, it's created. |
|---|
| 126 | |
|---|
| 127 | Returns a `CacheItem` instance. |
|---|
| 128 | """ |
|---|
| 129 | cacheItemID = md5(str(cacheItemID)).hexdigest() |
|---|
| 130 | |
|---|
| 131 | if not self._cacheItems.has_key(cacheItemID): |
|---|
| 132 | cacheItem = self._cacheItemClass( |
|---|
| 133 | cacheItemID=cacheItemID, cacheStore=self._wrappedCacheDataStore) |
|---|
| 134 | self._cacheItems[cacheItemID] = cacheItem |
|---|
| 135 | self._isNew = False |
|---|
| 136 | return self._cacheItems[cacheItemID] |
|---|