~ubuntu-branches/ubuntu/karmic/libapache2-mod-python/karmic-updates

« back to all changes in this revision

Viewing changes to lib/python/mod_python/cache.py

  • Committer: Bazaar Package Importer
  • Author(s): Piotr Ozarowski
  • Date: 2007-02-21 18:24:29 UTC
  • mfrom: (1.1.8 feisty)
  • Revision ID: james.westby@ubuntu.com-20070221182429-9okop7e0qpi24l85
Tags: 3.2.10-4
* Added XS-Vcs-Svn field
* Removed "db_purge" part from libapache2-mod-python.postrm
  (dh_installdebconf is generating a rule that will not fail if debconf is
  already removed)
* Added initial Spanish debconf translation from Manuel Porras Peralta.
  (closes: #411235)
* Added initial Portuguese debconf translation from Pedro Ribeiro.
  (closes: #411742)
* Added initial Galician debconf translation from Jacobo Tarrio.
  (closes: #411831)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
 # Copyright 2004 Apache Software Foundation 
 
3
 # 
 
4
 # Licensed under the Apache License, Version 2.0 (the "License"); you
 
5
 # may not use this file except in compliance with the License.  You
 
6
 # may obtain a copy of the License at
 
7
 #
 
8
 #      http://www.apache.org/licenses/LICENSE-2.0
 
9
 #
 
10
 # Unless required by applicable law or agreed to in writing, software
 
11
 # distributed under the License is distributed on an "AS IS" BASIS,
 
12
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 
13
 # implied.  See the License for the specific language governing
 
14
 # permissions and limitations under the License.
 
15
 #
 
16
 # Originally developed by Gregory Trubetskoy.
 
17
 # 
 
18
 # This was donated by Nicolas Lehuen, and also posted to the Python Cookbook
 
19
 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/302997
 
20
 # 
 
21
 # $Id: cache.py 374268 2006-02-02 05:31:45Z nlehuen $
 
22
 
 
23
from os import stat
 
24
from time import time, mktime
 
25
from rfc822 import parsedate
 
26
from calendar import timegm
 
27
import urllib2
 
28
import re
 
29
import weakref
 
30
import new
 
31
 
 
32
try:
 
33
    from threading import Lock
 
34
except ImportError:
 
35
    from dummy_threading import Lock
 
36
 
 
37
NOT_INITIALIZED = object()
 
38
 
 
39
class Entry(object):
 
40
    """ A cache entry, mostly an internal object. """
 
41
    def __init__(self, key):
 
42
        object.__init__(self)
 
43
        self._key=key
 
44
        self._value=NOT_INITIALIZED
 
45
        self._lock=Lock()
 
46
 
 
47
class Cache(object):
 
48
    """ An abstract, multi-threaded cache object. """
 
49
    
 
50
    def __init__(self, max_size=0):
 
51
        """ Builds a cache with a limit of max_size entries.
 
52
            If this limit is exceeded, the Least Recently Used entry is discarded.
 
53
            if max_size==0, the cache is unbounded (no LRU rule is applied).
 
54
        """
 
55
        object.__init__(self)
 
56
        self._maxsize=max_size
 
57
        self._dict={}
 
58
        self._lock=Lock()
 
59
        
 
60
        # Header of the access list
 
61
        if self._maxsize:
 
62
            self._head=Entry(None)
 
63
            self._head._previous=self._head
 
64
            self._head._next=self._head
 
65
 
 
66
    def __setitem__(self, name, value):
 
67
        """ Populates the cache with a given name and value. """
 
68
        key = self.key(name)
 
69
        
 
70
        entry = self._get_entry(key)
 
71
        
 
72
        entry._lock.acquire()
 
73
        try:
 
74
            self._pack(entry,value)
 
75
            self.commit()
 
76
        finally:
 
77
            entry._lock.release()
 
78
 
 
79
    def __getitem__(self, name):
 
80
        """ Gets a value from the cache, builds it if required.
 
81
        """
 
82
        return self._checkitem(name)[2]
 
83
 
 
84
    def __delitem__(self, name):
 
85
        self._lock.acquire()
 
86
        try:
 
87
            key = self.key(name)
 
88
            del self._dict[key]
 
89
        finally:
 
90
            self._lock.release()
 
91
 
 
92
    def _get_entry(self,key):
 
93
        self._lock.acquire()
 
94
        try:
 
95
            entry = self._dict.get(key)
 
96
            if not entry:
 
97
                entry = Entry(key)
 
98
                self._dict[key]=entry
 
99
                if self._maxsize:
 
100
                    entry._next = entry._previous = None
 
101
                    self._access(entry)
 
102
                    self._checklru()
 
103
            elif self._maxsize:
 
104
                self._access(entry)
 
105
            return entry
 
106
        finally:
 
107
            self._lock.release()
 
108
 
 
109
    def _checkitem(self, name):
 
110
        """ Gets a value from the cache, builds it if required.
 
111
            Returns a tuple is_new, key, value, entry.
 
112
            If is_new is True, the result had to be rebuilt.
 
113
        """
 
114
        key = self.key(name)
 
115
        
 
116
        entry = self._get_entry(key)
 
117
 
 
118
        entry._lock.acquire()
 
119
        try:
 
120
            value = self._unpack(entry)
 
121
            is_new = False
 
122
            if value is NOT_INITIALIZED:
 
123
                opened = self.check(key, name, entry)
 
124
                value = self.build(key, name, opened, entry)
 
125
                is_new = True
 
126
                self._pack(entry, value)
 
127
                self.commit()
 
128
            else:
 
129
                opened = self.check(key, name, entry)
 
130
                if opened is not None:
 
131
                    value = self.build(key, name, opened, entry)
 
132
                    is_new = True
 
133
                    self._pack(entry, value)
 
134
                    self.commit()
 
135
            return is_new, key, value, entry
 
136
        finally:
 
137
            entry._lock.release()
 
138
 
 
139
    def mru(self):
 
140
        """ Returns the Most Recently Used key """
 
141
        if self._maxsize:
 
142
            self._lock.acquire()
 
143
            try:
 
144
                return self._head._previous._key
 
145
            finally:
 
146
                self._lock.release()
 
147
        else:
 
148
            return None
 
149
 
 
150
    def lru(self):
 
151
        """ Returns the Least Recently Used key """
 
152
        if self._maxsize:
 
153
            self._lock.acquire()
 
154
            try:
 
155
                return self._head._next._key
 
156
            finally:
 
157
                self._lock.release()
 
158
        else:
 
159
            return None
 
160
 
 
161
    def key(self, name):
 
162
        """ Override this method to extract a key from the name passed to the [] operator """
 
163
        return name
 
164
 
 
165
    def commit(self):
 
166
        """ Override this method if you want to do something each time the underlying dictionary is modified (e.g. make it persistent). """
 
167
        pass
 
168
 
 
169
    def clear(self):
 
170
        """ Clears the cache """
 
171
        self._lock.acquire()
 
172
        try:
 
173
            self._dict.clear()
 
174
            if self._maxsize:
 
175
                self._head._next=self._head
 
176
                self._head._previous=self._head
 
177
        finally:
 
178
            self._lock.release()
 
179
 
 
180
    def check(self, key, name, entry):
 
181
        """ Override this method to check whether the entry with the given name is stale. Return None if it is fresh
 
182
            or an opened resource if it is stale. The object returned will be passed to the 'build' method as the 'opened' parameter.
 
183
            Use the 'entry' parameter to store meta-data if required. Don't worry about multiple threads accessing the same name,
 
184
            as this method is properly isolated.
 
185
        """
 
186
        return None
 
187
 
 
188
    def build(self, key, name, opened, entry):
 
189
        """ Build the cached value with the given name from the given opened resource. Use entry to obtain or store meta-data if needed.
 
190
             Don't worry about multiple threads accessing the same name, as this method is properly isolated.
 
191
        """
 
192
        raise NotImplementedError()
 
193
           
 
194
    def _access(self, entry):
 
195
        " Internal use only, must be invoked within a cache lock. Updates the access list. """
 
196
        if entry._next is not self._head:
 
197
            if entry._previous is not None:
 
198
                # remove the entry from the access list
 
199
                entry._previous._next=entry._next
 
200
                entry._next._previous=entry._previous
 
201
            # insert the entry at the end of the access list
 
202
            entry._previous=self._head._previous
 
203
            entry._previous._next=entry
 
204
            entry._next=self._head
 
205
            entry._next._previous=entry
 
206
            if self._head._next is self._head:
 
207
                self._head._next=entry
 
208
 
 
209
    def _checklru(self):
 
210
        " Internal use only, must be invoked within a cache lock. Removes the LRU entry if needed. """
 
211
        if len(self._dict)>self._maxsize:
 
212
            lru=self._head._next
 
213
            lru._previous._next=lru._next
 
214
            lru._next._previous=lru._previous
 
215
            del self._dict[lru._key]
 
216
 
 
217
    def _pack(self, entry, value):
 
218
        """ Store the value in the entry. """
 
219
        entry._value=value
 
220
 
 
221
    def _unpack(self, entry):
 
222
        """ Recover the value from the entry, returns NOT_INITIALIZED if it is not OK. """
 
223
        return entry._value
 
224
 
 
225
class WeakCache(Cache):
 
226
    """ This cache holds weak references to the values it stores. Whenever a value is not longer
 
227
        normally referenced, it is removed from the cache. Useful for sharing the result of long
 
228
        computations but letting them go as soon as they are not needed by anybody.
 
229
    """
 
230
        
 
231
    def _pack(self, entry, value):
 
232
        entry._value=weakref.ref(value, lambda ref: self.__delitem__(entry._key))
 
233
        
 
234
    def _unpack(self, entry):
 
235
        if entry._value is NOT_INITIALIZED:
 
236
            return NOT_INITIALIZED
 
237
            
 
238
        value = entry._value()
 
239
        if value is None:
 
240
            return NOT_INITIALIZED
 
241
        else:
 
242
            return value
 
243
 
 
244
class FileCache(Cache):
 
245
    """ A file cache. Returns the content of the files as a string, given their filename.
 
246
        Whenever the files are modified (according to their modification time) the cache is updated.
 
247
        Override the build method to obtain more interesting behaviour.
 
248
    """
 
249
    def __init__(self, max_size=0, mode='rb'):
 
250
        Cache.__init__(self, max_size)
 
251
        self.mode=mode
 
252
    
 
253
    def check(self, key, name, entry):
 
254
        timestamp = stat(key).st_mtime 
 
255
 
 
256
        if entry._value is NOT_INITIALIZED:
 
257
            entry._timestamp = timestamp
 
258
            return file(key, self.mode)
 
259
        else:
 
260
            if entry._timestamp != timestamp:
 
261
                entry._timestamp = timestamp
 
262
                return file(key, self.mode)
 
263
            else:
 
264
                return None
 
265
 
 
266
    def build(self, key, name, opened, entry):
 
267
        """ Return the content of the file as a string. Override this for better behaviour. """
 
268
        try:
 
269
            return opened.read()
 
270
        finally:
 
271
            opened.close()
 
272
 
 
273
def parseRFC822Time(t):
 
274
    return mktime(parsedate(t))
 
275
 
 
276
re_max_age=re.compile('max-age\s*=\s*(\d+)', re.I)
 
277
 
 
278
class HTTPEntity(object):
 
279
    def __init__(self, entity, metadata):
 
280
        self.entity=entity
 
281
        self.metadata=metadata
 
282
    
 
283
    def __repr__(self):
 
284
        return 'HTTPEntity(%s, %s)'%(repr(self.entity), self.metadata)
 
285
        
 
286
    def __str__(self):
 
287
        return self.entity
 
288
 
 
289
class HTTPCache(Cache):
 
290
    """ An HTTP cache. Returns the entity found at the given URL.
 
291
        Uses Expires, ETag and Last-Modified headers to minimize bandwidth usage.
 
292
        Partial Cache-Control support (only max-age is supported).
 
293
    """
 
294
    def check(self, key, name, entry):
 
295
        request = urllib2.Request(key)
 
296
        
 
297
        try:
 
298
            if time()<entry._expires:
 
299
                return None
 
300
        except AttributeError:
 
301
            pass            
 
302
        try:
 
303
            header, value = entry._validator
 
304
            request.headers[header]=value
 
305
        except AttributeError:
 
306
            pass
 
307
        opened = None
 
308
        try:
 
309
            opened = urllib2.urlopen(request)
 
310
            headers = opened.info()
 
311
 
 
312
            # expiration handling            
 
313
            expiration = False
 
314
            try:
 
315
                match = re_max_age.match(headers['cache-control'])
 
316
                if match:
 
317
                        entry._expires=time()+int(match.group(1))
 
318
                        expiration = True
 
319
            except (KeyError, ValueError):
 
320
                pass
 
321
            if not expiration:
 
322
                try:
 
323
                    date = parseRFC822Time(headers['date'])
 
324
                    expires = parseRFC822Time(headers['expires'])
 
325
                    entry._expires = time()+(expires-date)
 
326
                    expiration = True
 
327
                except KeyError:
 
328
                    pass
 
329
            
 
330
            # validator handling
 
331
            validation = False
 
332
            try:
 
333
                entry._validator='If-None-Match', headers['etag']
 
334
                validation = True
 
335
            except KeyError:
 
336
                pass
 
337
            if not validation:
 
338
                try:
 
339
                    entry._validator='If-Modified-Since', headers['last-modified']
 
340
                except KeyError:
 
341
                    pass
 
342
 
 
343
            return opened
 
344
        except urllib2.HTTPError, error:
 
345
            if opened: opened.close()
 
346
            if error.code==304:
 
347
                return None
 
348
            else:
 
349
                raise error
 
350
 
 
351
    def build(self, key, name, opened, entry):
 
352
        try:
 
353
            return HTTPEntity(opened.read(), dict(opened.info()))
 
354
        finally:
 
355
            opened.close()
 
356
 
 
357
re_not_word = re.compile(r'\W+')
 
358
 
 
359
class ModuleCache(FileCache):
 
360
    """ A module cache. Give it a file name, it returns a module
 
361
        which results from the execution of the Python script it contains.
 
362
        This module is not inserted into sys.modules.
 
363
    """
 
364
    def __init__(self, max_size=0):
 
365
        FileCache.__init__(self, max_size, 'r')
 
366
    
 
367
    def build(self, key, name, opened, entry):
 
368
        try:
 
369
            module = new.module(re_not_word.sub('_',key))
 
370
            module.__file__ = key
 
371
            exec opened in module.__dict__
 
372
            return module
 
373
        finally:
 
374
            opened.close()
 
375
 
 
376
class HttpModuleCache(HTTPCache):
 
377
    """ A module cache. Give it an HTTP URL, it returns a module
 
378
        which results from the execution of the Python script it contains.
 
379
        This module is not inserted into sys.modules.
 
380
    """
 
381
    def __init__(self, max_size=0):
 
382
        HTTPCache.__init__(self, max_size)
 
383
    
 
384
    def build(self, key, name, opened, entry):
 
385
        try:
 
386
            module = new.module(re_not_word.sub('_',key))
 
387
            module.__file__ = key
 
388
            text = opened.read().replace('\r\n', '\n')
 
389
            code = compile(text, name, 'exec')
 
390
            exec code in module.__dict__
 
391
            return module
 
392
        finally:
 
393
            opened.close()
 
394
 
 
395
class FunctionCache(Cache):
 
396
    def __init__(self, function, max_size=0):
 
397
        Cache.__init__(self, max_size)
 
398
        self.function=function
 
399
    
 
400
    def __call__(self, *args, **kw):
 
401
        if kw:
 
402
            # a dict is not hashable so we build a tuple of (key, value) pairs
 
403
            kw = tuple(kw.iteritems())
 
404
            return self[args, kw]
 
405
        else:
 
406
            return self[args, ()]
 
407
    
 
408
    def build(self, key, name, opened, entry):
 
409
        args, kw = key
 
410
        return self.function(*args, **dict(kw))