source: orange-bioinformatics/obiKEGG2/caching.py @ 1603:7a78a7302c20

Revision 1603:7a78a7302c20, 7.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Configurable cache invalidation.

Line 
1"""
2Caching framework for cached kegg api calls.
3 
4"""
5import os
6import UserDict
7import sqlite3
8import cPickle as pickle
9
10from datetime import datetime, date, timedelta
11from . import conf
12
13class Store(object):
14    def __init__(self):
15        self.timestamp = 0
16       
17    def open(self):
18        raise NotImplementedError
19   
20    def __enter__(self):
21        return self
22   
23    def __exit__(self, *args):
24        pass
25   
26
27class Sqlite3Store(Store, UserDict.DictMixin):
28    def __init__(self, filename):
29        self.filename = filename
30        self.con = sqlite3.connect(filename)
31        self.con.execute("""
32        CREATE TABLE IF NOT EXISTS cache
33            (key TEXT UNIQUE,
34             value TEXT
35            )
36        """)
37        self.con.commit()
38       
39    def __getitem__(self, key):
40        cur = self.con.execute("""
41            SELECT value
42            FROM cache
43            WHERE key=?
44        """, (key,))
45        r = cur.fetchall()
46       
47        if not r:
48            raise KeyError(key)
49        else:
50            return pickle.loads(str(r[0][0]))
51   
52    def __setitem__(self, key, value):
53        value = pickle.dumps(value)
54        self.con.execute("""
55            INSERT OR REPLACE INTO cache
56            VALUES (?, ?)
57        """, (key, value))
58        self.con.commit()
59       
60    def __delitem__(self, key):
61        self.con.execute("""
62            DELETE FROM cache
63            WHERE key=?
64        """, (key,))
65        self.con.commit()
66       
67    def keys(self):
68        cur = self.con.execute("""
69            SELECT key
70            FROM cache
71        """)
72        return [str(r[0]) for r in cur.fetchall()]
73       
74    def close(self):
75        pass
76   
77   
78class DictStore(Store, UserDict.DictMixin):
79    def __init__(self):
80        Store.__init__(self)
81       
82    def close(self):
83        pass
84   
85   
86from functools import wraps
87from contextlib import closing
88
89
90class cache_entry(object):
91    def __init__(self, value, mtime=None, expires=None):
92        self.value = value
93        self.mtime = mtime
94        self.expires = expires
95       
96_SESSION_START = datetime.now()
97
98class cached_wrapper(object):
99    """ TODO: needs documentation
100    """
101    def __init__(self, function, instance, class_, cache_store, last_modified=None):
102        self.function = function
103        self.instance = instance
104        self.class_ = class_
105        self.cache_store = cache_store
106        self.last_modified = last_modified
107       
108    def has_key(self, key):
109        with closing(self.cache_store()) as store:
110            return key in store
111   
112    def key_from_args(self, args, kwargs=None):
113        key = self.function.__name__ + repr(args)
114        return key
115   
116    def invalidate_key(self, key):
117        with closing(self.cache_store()) as store:
118            del store[key]
119           
120    def last_modified_from_args(self, args, kwargs=None):
121        key = self.key_from_args(args, kwargs)
122        if self.instance is not None:
123            self.instance.last_modified(args)
124       
125    def invalidate_args(self, args):
126        return self.invalidate_key(self.key_from_args(args))
127       
128    def invalidate_all(self):
129        prefix = self.key_from_args(()).rstrip(",)")
130        with self.cache_store() as store:
131            for key in store.keys():
132                if key.startswith(prefix):
133                    del store[key]
134   
135    def memoize(self, args, kwargs, value, timestamp=None):
136        key = self.key_from_args(args, kwargs)
137        if timestamp is None:
138            timestamp = datetime.now()
139           
140        with closing(self.cache_store()) as store:
141            store[key] = cache_entry(value, mtime=timestamp)
142       
143    def __call__(self, *args):
144        key = self.key_from_args(args)
145        with closing(self.cache_store()) as store:
146            valid = True
147            if key not in store:
148                valid = False
149            else:
150                entry = store[key]
151                rval = entry.value
152               
153                if not self.is_entry_valid(entry, args):
154                    valid = False
155            if not valid:
156                rval = self.function(self.instance, *args)
157                store[key] = cache_entry(rval, datetime.now(), None)
158       
159        return rval
160       
161    def min_timestamp(self, args):
162        key = self.key_from_args(args)
163        return datetime.fromtimestamp(0)
164   
165    def is_entry_valid(self, entry, args):
166        # Need to check datetime first (it subclasses date)
167        if isinstance(entry.mtime, datetime):
168            mtime = entry.mtime
169        elif isinstance(entry.mtime, date):
170            mtime = datetime(entry.mtime.year, entry.mtime.month,
171                             entry.mtime.day, 1, 1, 1)
172        else:
173            return False
174       
175        if self.min_timestamp(args) > mtime:
176            return False
177       
178        last_modified = self.last_modified_from_args(args)
179       
180        if isinstance(last_modified, date):
181            last_modified = datetime(last_modified.year, last_modified.month,
182                                     last_modified.day, 1, 1, 1)
183        elif isinstance(last_modified, basestring):
184            # Could have settable format
185            mtime = mtime.strftime("%Y %m %d %H %M %S") 
186       
187        elif last_modified is None:
188            if conf.params["cache.invalidate"] == "always":
189                return False
190            elif conf.params["cache.invalidate"] == "session":
191                last_modified = _SESSION_START
192            elif conf.params["cache.invalidate"] == "daily":
193                last_modified = datetime.now().replace(hour=0, minute=0,
194                                                       second=0, microsecond=0)
195            elif conf.params["cache.invalidate"] == "weekly":
196                last_modified = datetime.now() - timedelta(7)
197            else: # ???
198                pass
199        if last_modified > mtime:
200            return False
201       
202        return self.min_timestamp(args) < entry.mtime
203   
204       
205class cached_method(object):
206    def __init__(self, function):
207        self.function = function
208       
209    def __get__(self, instance, owner):
210        if instance is not None:
211            return cached_wrapper(self.function, instance, owner,
212                                  self.get_cache_store(instance, owner))
213        return self
214   
215    def get_cache_store(self, instance, owner):
216        if hasattr(instance, "cache_store"):
217            return instance.cache_store
218        elif not hasattr("_cached_method_cache"):
219            instance._cached_method_cache = DictStore()
220        return instance._cached_method_cache
221
222
223class bget_cached_method(cached_method):
224    def __get__(self, instance, owner):
225        if instance is not None:
226            return cached_wrapper(self.function, instance, owner,
227                                  self.get_cache_store(instance, owner),
228                                  self.get_last_modified(instance, owner))
229        return self
230   
231    def get_last_modified(self, instance, owner):
232        if hasattr(instance, "last_modified"):
233            return instance.last_modified
234   
235def touch_dir(path):
236    path = os.path.expanduser(path)
237    if not os.path.exists(path):
238        os.makedirs(path)
239   
Note: See TracBrowser for help on using the repository browser.