1 | """Cache object |
---|
2 | |
---|
3 | The Cache object is used to manage a set of cache files and their |
---|
4 | associated backend. The backends can be rotated on the fly by |
---|
5 | specifying an alternate type when used. |
---|
6 | |
---|
7 | Advanced users can add new backends in beaker.backends |
---|
8 | |
---|
9 | """ |
---|
10 | import pkg_resources |
---|
11 | import warnings |
---|
12 | |
---|
13 | import beaker.container as container |
---|
14 | import beaker.util as util |
---|
15 | from beaker.exceptions import BeakerException, InvalidCacheBackendError |
---|
16 | |
---|
17 | # Initialize the basic available backends |
---|
18 | clsmap = { |
---|
19 | 'memory':container.MemoryNamespaceManager, |
---|
20 | 'dbm':container.DBMNamespaceManager, |
---|
21 | 'file':container.FileNamespaceManager, |
---|
22 | } |
---|
23 | |
---|
24 | |
---|
25 | # Load up the additional entry point defined backends |
---|
26 | for entry_point in pkg_resources.iter_entry_points('beaker.backends'): |
---|
27 | try: |
---|
28 | NamespaceManager = entry_point.load() |
---|
29 | name = entry_point.name |
---|
30 | if name in clsmap: |
---|
31 | raise BeakerException("NamespaceManager name conflict,'%s' " |
---|
32 | "already loaded" % name) |
---|
33 | clsmap[name] = NamespaceManager |
---|
34 | except (InvalidCacheBackendError, SyntaxError): |
---|
35 | # Ignore invalid backends |
---|
36 | pass |
---|
37 | except: |
---|
38 | import sys |
---|
39 | from pkg_resources import DistributionNotFound |
---|
40 | # Warn when there's a problem loading a NamespaceManager |
---|
41 | if not isinstance(sys.exc_info()[1], DistributionNotFound): |
---|
42 | import traceback |
---|
43 | from StringIO import StringIO |
---|
44 | tb = StringIO() |
---|
45 | traceback.print_exc(file=tb) |
---|
46 | warnings.warn("Unable to load NamespaceManager entry point: '%s': " |
---|
47 | "%s" % (entry_point, tb.getvalue()), RuntimeWarning, |
---|
48 | 2) |
---|
49 | |
---|
50 | |
---|
51 | # Load legacy-style backends |
---|
52 | try: |
---|
53 | import beaker.ext.memcached as memcached |
---|
54 | clsmap['ext:memcached'] = memcached.MemcachedNamespaceManager |
---|
55 | except InvalidCacheBackendError, e: |
---|
56 | clsmap['ext:memcached'] = e |
---|
57 | |
---|
58 | try: |
---|
59 | import beaker.ext.database as database |
---|
60 | clsmap['ext:database'] = database.DatabaseNamespaceManager |
---|
61 | except InvalidCacheBackendError, e: |
---|
62 | clsmap['ext:database'] = e |
---|
63 | |
---|
64 | try: |
---|
65 | import beaker.ext.sqla as sqla |
---|
66 | clsmap['ext:sqla'] = sqla.SqlaNamespaceManager |
---|
67 | except InvalidCacheBackendError, e: |
---|
68 | clsmap['ext:sqla'] = e |
---|
69 | |
---|
70 | try: |
---|
71 | import beaker.ext.google as google |
---|
72 | clsmap['ext:google'] = google.GoogleNamespaceManager |
---|
73 | except (InvalidCacheBackendError, SyntaxError), e: |
---|
74 | clsmap['ext:google'] = e |
---|
75 | |
---|
76 | |
---|
77 | class Cache(object): |
---|
78 | """Front-end to the containment API implementing a data cache. |
---|
79 | |
---|
80 | ``namespace`` |
---|
81 | the namespace of this Cache |
---|
82 | |
---|
83 | ``type`` |
---|
84 | type of cache to use |
---|
85 | |
---|
86 | ``expire`` |
---|
87 | seconds to keep cached data |
---|
88 | |
---|
89 | ``expiretime`` |
---|
90 | seconds to keep cached data (legacy support) |
---|
91 | |
---|
92 | ``starttime`` |
---|
93 | time when cache was cache was |
---|
94 | """ |
---|
95 | def __init__(self, namespace, type='memory', expiretime=None, |
---|
96 | starttime=None, expire=None, **nsargs): |
---|
97 | try: |
---|
98 | cls = clsmap[type] |
---|
99 | if isinstance(cls, InvalidCacheBackendError): |
---|
100 | raise cls |
---|
101 | except KeyError: |
---|
102 | raise TypeError("Unknown cache implementation %r" % type) |
---|
103 | |
---|
104 | self.namespace = cls(namespace, **nsargs) |
---|
105 | self.expiretime = expiretime or expire |
---|
106 | self.starttime = starttime |
---|
107 | self.nsargs = nsargs |
---|
108 | |
---|
109 | def put(self, key, value, **kw): |
---|
110 | self._get_value(key, **kw).set_value(value) |
---|
111 | set_value = put |
---|
112 | |
---|
113 | def get(self, key, **kw): |
---|
114 | """Retrieve a cached value from the container""" |
---|
115 | return self._get_value(key, **kw).get_value() |
---|
116 | get_value = get |
---|
117 | |
---|
118 | def remove_value(self, key, **kw): |
---|
119 | mycontainer = self._get_value(key, **kw) |
---|
120 | if mycontainer.has_current_value(): |
---|
121 | mycontainer.clear_value() |
---|
122 | remove = remove_value |
---|
123 | |
---|
124 | def _get_value(self, key, **kw): |
---|
125 | if isinstance(key, unicode): |
---|
126 | key = key.encode('ascii', 'backslashreplace') |
---|
127 | |
---|
128 | if 'type' in kw: |
---|
129 | return self._legacy_get_value(key, **kw) |
---|
130 | |
---|
131 | kw.setdefault('expiretime', self.expiretime) |
---|
132 | kw.setdefault('starttime', self.starttime) |
---|
133 | |
---|
134 | return container.Value(key, self.namespace, **kw) |
---|
135 | |
---|
136 | def _legacy_get_value(self, key, type, **kw): |
---|
137 | expiretime = kw.pop('expiretime', self.expiretime) |
---|
138 | starttime = kw.pop('starttime', None) |
---|
139 | createfunc = kw.pop('createfunc', None) |
---|
140 | kwargs = self.nsargs.copy() |
---|
141 | kwargs.update(kw) |
---|
142 | c = Cache(self.namespace.namespace, type=type, **kwargs) |
---|
143 | return c._get_value(key, expiretime=expiretime, createfunc=createfunc, |
---|
144 | starttime=starttime) |
---|
145 | _legacy_get_value = util.deprecated(_legacy_get_value, "Specifying a " |
---|
146 | "'type' and other namespace configuration with cache.get()/put()/etc. " |
---|
147 | "is deprecated. Specify 'type' and other namespace configuration to " |
---|
148 | "cache_manager.get_cache() and/or the Cache constructor instead.") |
---|
149 | |
---|
150 | def clear(self): |
---|
151 | """Clear all the values from the namespace""" |
---|
152 | self.namespace.remove() |
---|
153 | |
---|
154 | # dict interface |
---|
155 | def __getitem__(self, key): |
---|
156 | return self.get(key) |
---|
157 | |
---|
158 | def __contains__(self, key): |
---|
159 | return self._get_value(key).has_current_value() |
---|
160 | |
---|
161 | def has_key(self, key): |
---|
162 | return key in self |
---|
163 | |
---|
164 | def __delitem__(self, key): |
---|
165 | self.remove_value(key) |
---|
166 | |
---|
167 | def __setitem__(self, key, value): |
---|
168 | self.put(key, value) |
---|
169 | |
---|
170 | |
---|
171 | class CacheManager(object): |
---|
172 | def __init__(self, **kwargs): |
---|
173 | """Initialize a CacheManager object with a set of options |
---|
174 | |
---|
175 | Options should be parsed with the |
---|
176 | :func:`~beaker.util.parse_cache_config_options` function to |
---|
177 | ensure only valid options are used. |
---|
178 | |
---|
179 | """ |
---|
180 | self.kwargs = kwargs |
---|
181 | self.caches = {} |
---|
182 | self.regions = kwargs.pop('cache_regions', {}) |
---|
183 | |
---|
184 | def get_cache(self, name, **kwargs): |
---|
185 | kw = self.kwargs.copy() |
---|
186 | kw.update(kwargs) |
---|
187 | return self.caches.setdefault(name + str(kw), Cache(name, **kw)) |
---|
188 | |
---|
189 | def get_cache_region(self, name, region): |
---|
190 | if region not in self.regions: |
---|
191 | raise BeakerException('Cache region not configured: %s' % region) |
---|
192 | kw = self.regions[region] |
---|
193 | return self.caches.setdefault(name + str(kw), Cache(name, **kw)) |
---|
194 | |
---|
195 | def region(self, region, *args): |
---|
196 | """Decorate a function to cache itself using a cache region |
---|
197 | |
---|
198 | The region decorator requires arguments if there are more than |
---|
199 | 2 of the same named function, in the same module. This is |
---|
200 | because the namespace used for the functions cache is based on |
---|
201 | the functions name and the module. |
---|
202 | |
---|
203 | |
---|
204 | Example:: |
---|
205 | |
---|
206 | # Assuming a cache object is available like: |
---|
207 | cache = CacheManager(dict_of_config_options) |
---|
208 | |
---|
209 | |
---|
210 | def populate_things(): |
---|
211 | |
---|
212 | @cache.region('short_term', 'some_data') |
---|
213 | def load(search_term, limit, offset): |
---|
214 | return load_the_data(search_term, limit, offset) |
---|
215 | |
---|
216 | return load('rabbits', 20, 0) |
---|
217 | |
---|
218 | .. note:: |
---|
219 | |
---|
220 | The function being decorated must only be called with |
---|
221 | positional arguments. |
---|
222 | |
---|
223 | """ |
---|
224 | cache = [None] |
---|
225 | key = " ".join(str(x) for x in args) |
---|
226 | |
---|
227 | def decorate(func): |
---|
228 | def cached(*args): |
---|
229 | reg = self.regions[region] |
---|
230 | if not reg.get('enabled', True): |
---|
231 | return func(*args) |
---|
232 | |
---|
233 | if not cache[0]: |
---|
234 | namespace = util.func_namespace(func) |
---|
235 | cache[0] = self.get_cache_region(namespace, region) |
---|
236 | |
---|
237 | cache_key = key + " " + " ".join(str(x) for x in args) |
---|
238 | def go(): |
---|
239 | return func(*args) |
---|
240 | |
---|
241 | return cache[0].get_value(cache_key, createfunc=go) |
---|
242 | return cached |
---|
243 | return decorate |
---|
244 | |
---|
245 | def cache(self, *args, **kwargs): |
---|
246 | """Decorate a function to cache itself with supplied parameters |
---|
247 | |
---|
248 | ``args`` |
---|
249 | used to make the key unique for this function, as in region() |
---|
250 | above. |
---|
251 | |
---|
252 | ``kwargs`` |
---|
253 | parameters to be passed to get_cache(), will override defaults |
---|
254 | |
---|
255 | Example:: |
---|
256 | |
---|
257 | # Assuming a cache object is available like: |
---|
258 | cache = CacheManager(dict_of_config_options) |
---|
259 | |
---|
260 | |
---|
261 | def populate_things(): |
---|
262 | |
---|
263 | @cache.cache('mycache', expire=15) |
---|
264 | def load(search_term, limit, offset): |
---|
265 | return load_the_data(search_term, limit, offset) |
---|
266 | |
---|
267 | return load('rabbits', 20, 0) |
---|
268 | |
---|
269 | .. note:: |
---|
270 | |
---|
271 | The function being decorated must only be called with |
---|
272 | positional arguments. |
---|
273 | |
---|
274 | """ |
---|
275 | cache = [None] |
---|
276 | key = " ".join(str(x) for x in args) |
---|
277 | |
---|
278 | def decorate(func): |
---|
279 | def cached(*args): |
---|
280 | if not cache[0]: |
---|
281 | namespace = util.func_namespace(func) |
---|
282 | cache[0] = self.get_cache(namespace, **kwargs) |
---|
283 | cache_key = key + " " + " ".join(str(x) for x in args) |
---|
284 | def go(): |
---|
285 | return func(*args) |
---|
286 | |
---|
287 | return cache[0].get_value(cache_key, createfunc=go) |
---|
288 | return cached |
---|
289 | return decorate |
---|