|
Package Dropbox ::
Package web2py ::
Package gluon ::
Module cache
|
|
1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8
9 Basic caching classes and methods
10 =================================
11
12 - Cache - The generic caching object interfacing with the others
13 - CacheInRam - providing caching in ram
14 - CacheOnDisk - provides caches on disk
15
16 Memcache is also available via a different module (see gluon.contrib.memcache)
17
18 When web2py is running on Google App Engine,
19 caching will be provided by the GAE memcache
20 (see gluon.contrib.gae_memcache)
21 """
22 import traceback
23 import time
24 import portalocker
25 import shelve
26 import thread
27 import os
28 import logging
29 import re
30 try:
31 import settings
32 have_settings = True
33 except ImportError:
34 have_settings = False
35
36 logger = logging.getLogger("web2py.cache")
37
38 __all__ = ['Cache', 'lazy_cache']
39
40
41 DEFAULT_TIME_EXPIRE = 300
45 """
46 Abstract class for cache implementations.
47 Main function is now to provide referenced api documentation.
48
49 Use CacheInRam or CacheOnDisk instead which are derived from this class.
50
51 Attentions, Michele says:
52
53 There are signatures inside gdbm files that are used directly
54 by the python gdbm adapter that often are lagging behind in the
55 detection code in python part.
56 On every occasion that a gdbm store is probed by the python adapter,
57 the probe fails, because gdbm file version is newer.
58 Using gdbm directly from C would work, because there is backward
59 compatibility, but not from python!
60 The .shelve file is discarded and a new one created (with new
61 signature) and it works until it is probed again...
62 The possible consequences are memory leaks and broken sessions.
63 """
64
65 cache_stats_name = 'web2py_cache_statistics'
66
68 """
69 Paremeters
70 ----------
71 request:
72 the global request object
73 """
74 raise NotImplementedError
75
78 """
79 Tries retrieve the value corresponding to `key` from the cache of the
80 object exists and if it did not expire, else it called the function `f`
81 and stores the output in the cache corresponding to `key`. In the case
82 the output of the function is returned.
83
84 :param key: the key of the object to be store or retrieved
85 :param f: the function, whose output is to be cached
86 :param time_expire: expiration of the cache in microseconds
87
88 - `time_expire` is used to compare the current time with the time when
89 the requested object was last saved in cache. It does not affect
90 future requests.
91 - Setting `time_expire` to 0 or negative value forces the cache to
92 refresh.
93
94 If the function `f` is `None` the cache is cleared.
95 """
96 raise NotImplementedError
97
98 - def clear(self, regex=None):
99 """
100 Clears the cache of all keys that match the provided regular expression.
101 If no regular expression is provided, it clears all entries in cache.
102
103 Parameters
104 ----------
105 regex:
106 if provided, only keys matching the regex will be cleared.
107 Otherwise all keys are cleared.
108 """
109
110 raise NotImplementedError
111
113 """
114 Increments the cached value for the given key by the amount in value
115
116 Parameters
117 ----------
118 key:
119 key for the cached object to be incremeneted
120 value:
121 amount of the increment (defaults to 1, can be negative)
122 """
123 raise NotImplementedError
124
125 - def _clear(self, storage, regex):
126 """
127 Auxiliary function called by `clear` to search and clear cache entries
128 """
129 r = re.compile(regex)
130 for (key, value) in storage.items():
131 if r.match(str(key)):
132 del storage[key]
133
136 """
137 Ram based caching
138
139 This is implemented as global (per process, shared by all threads)
140 dictionary.
141 A mutex-lock mechanism avoid conflicts.
142 """
143
144 locker = thread.allocate_lock()
145 meta_storage = {}
146
151
169
170 - def clear(self, regex=None):
184
188 """
189 Attention! cache.ram does not copy the cached object. It just stores a reference to it.
190 Turns out the deepcopying the object has some problems:
191 1) would break backward compatibility
192 2) would be limiting because people may want to cache live objects
193 3) would work unless we deepcopy no storage and retrival which would make things slow.
194 Anyway. You can deepcopy explicitly in the function generating the value to be cached.
195 """
196 self.initialize()
197
198 dt = time_expire
199 now = time.time()
200
201 self.locker.acquire()
202 item = self.storage.get(key, None)
203 if item and f is None:
204 del self.storage[key]
205 if destroyer:
206 destroyer(item[1])
207 self.storage[CacheAbstract.cache_stats_name]['hit_total'] += 1
208 self.locker.release()
209
210 if f is None:
211 return None
212 if item and (dt is None or item[0] > now - dt):
213 return item[1]
214 elif item and (item[0] < now - dt) and destroyer:
215 destroyer(item[1])
216 value = f()
217
218 self.locker.acquire()
219 self.storage[key] = (now, value)
220 self.storage[CacheAbstract.cache_stats_name]['misses'] += 1
221 self.locker.release()
222 return value
223
236
239 """
240 Disk based cache
241
242 This is implemented as a shelve object and it is shared by multiple web2py
243 processes (and threads) as long as they share the same filesystem.
244 The file is locked wen accessed.
245
246 Disk cache provides persistance when web2py is started/stopped but it slower
247 than `CacheInRam`
248
249 Values stored in disk cache must be pickable.
250 """
251
261
302
303 - def __init__(self, request=None, folder=None):
308
310 if self.initialized:
311 return
312 else:
313 self.initialized = True
314 folder = self.folder
315 request = self.request
316
317
318
319 folder = folder or os.path.join(request.folder, 'cache')
320
321 if not os.path.exists(folder):
322 os.mkdir(folder)
323
324
325
326 self.locker_name = os.path.join(folder, 'cache.lock')
327 self.shelve_name = os.path.join(folder, 'cache.shelve')
328
329 - def clear(self, regex=None):
340
366
378
381 - def __init__(self, func, key, time_expire, cache, cache_model):
382 self.__name__ = func.__name__
383 self.__doc__ = func.__doc__
384 self.func = func
385 self.key = key
386 self.time_expire = time_expire
387 self.cache = cache
388 self.cache_model = cache_model
389
391 if not self.key:
392 key2 = self.__name__ + ':' + repr(a) + ':' + repr(b)
393 else:
394 key2 = self.key.replace('%(name)s', self.__name__)\
395 .replace('%(args)s', str(a)).replace('%(vars)s', str(b))
396 cache_model = self.cache_model
397 if not cache_model or isinstance(cache_model, str):
398 cache_model = getattr(self.cache, cache_model or 'ram')
399 return cache_model(key2,
400 lambda a=a, b=b: self.func(*a, **b),
401 self.time_expire)
402
405 """
406 Sets up generic caching, creating an instance of both CacheInRam and
407 CacheOnDisk.
408 In case of GAE will make use of gluon.contrib.gae_memcache.
409
410 - self.ram is an instance of CacheInRam
411 - self.disk is an instance of CacheOnDisk
412 """
413
414 autokey = ':%(name)s:%(args)s:%(vars)s'
415
417 """
418 Parameters
419 ----------
420 request:
421 the global request object
422 """
423
424 if have_settings and settings.global_settings.web2py_runtime_gae:
425 from contrib.gae_memcache import MemcacheClient
426 self.ram = self.disk = MemcacheClient(request)
427 else:
428
429 self.ram = CacheInRam(request)
430 try:
431 self.disk = CacheOnDisk(request)
432 except IOError:
433 logger.warning('no cache.disk (IOError)')
434 except AttributeError:
435
436
437 logger.warning('no cache.disk (AttributeError)')
438
443 """
444 Decorator function that can be used to cache any function/method.
445
446 Example::
447
448 @cache('key', 5000, cache.ram)
449 def f():
450 return time.ctime()
451
452 When the function f is called, web2py tries to retrieve
453 the value corresponding to `key` from the cache of the
454 object exists and if it did not expire, else it calles the function `f`
455 and stores the output in the cache corresponding to `key`. In the case
456 the output of the function is returned.
457
458 :param key: the key of the object to be store or retrieved
459 :param time_expire: expiration of the cache in microseconds
460 :param cache_model: "ram", "disk", or other
461 (like "memcache" if defined). It defaults to "ram".
462
463 Notes
464 -----
465 `time_expire` is used to compare the curret time with the time when the
466 requested object was last saved in cache. It does not affect future
467 requests.
468 Setting `time_expire` to 0 or negative value forces the cache to
469 refresh.
470
471 If the function `f` is an action, we suggest using
472 `request.env.path_info` as key.
473 """
474
475 def tmp(func, cache=self, cache_model=cache_model):
476 return CacheAction(func, key, time_expire, self, cache_model)
477 return tmp
478
479 @staticmethod
481 """
482 allow replacing cache.ram with cache.with_prefix(cache.ram,'prefix')
483 it will add prefix to all the cache keys used.
484 """
485 return lambda key, f, time_expire=DEFAULT_TIME_EXPIRE, prefix=prefix:\
486 cache_model(prefix + key, f, time_expire)
487
488
489 -def lazy_cache(key=None, time_expire=None, cache_model='ram'):
490 """
491 can be used to cache any function including in modules,
492 as long as the cached function is only called within a web2py request
493 if a key is not provided, one is generated from the function name
494 the time_expire defaults to None (no cache expiration)
495 if cache_model is "ram" then the model is current.cache.ram, etc.
496 """
497 def decorator(f, key=key, time_expire=time_expire, cache_model=cache_model):
498 key = key or repr(f)
499
500 def g(*c, **d):
501 from gluon import current
502 return current.cache(key, time_expire, cache_model)(f)(*c, **d)
503 g.__name__ = f.__name__
504 return g
505 return decorator
506