source: orange-bioinformatics/_bioinformatics/obiKEGG2/caching.py @ 1712:bcd52ead3d93

Revision 1712:bcd52ead3d93, 7.9 KB checked in by markotoplak, 20 months ago (diff)

Classes for storing gene set data moved to Orange.bio.geneset. obiGeneSets adapted to make new pickled classes.

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
26class Sqlite3Store(Store, UserDict.DictMixin):
27    def __init__(self, filename):
28        self.filename = filename
29        self.con = sqlite3.connect(filename)
30        #self.con = sqlite3.connect(":memory:")
31        self.con.execute("""
32        CREATE TABLE IF NOT EXISTS cache
33            (key TEXT UNIQUE,
34             value TEXT
35            )
36        """)
37        self.con.execute("""
38        CREATE INDEX IF NOT EXISTS cache_index
39        ON cache (key)
40        """)
41        self.con.commit()
42       
43    def __getitem__(self, key):
44        cur = self.con.execute("""
45            SELECT value
46            FROM cache
47            WHERE key=?
48        """, (key,))
49        r = cur.fetchall()
50       
51        if not r:
52            raise KeyError(key)
53        else:
54            return pickle.loads(str(r[0][0]))
55   
56    def __setitem__(self, key, value):
57        value = pickle.dumps(value)
58        self.con.execute("""
59            INSERT OR REPLACE INTO cache
60            VALUES (?, ?)
61        """, (key, value))
62        self.con.commit()
63       
64    def __delitem__(self, key):
65        self.con.execute("""
66            DELETE FROM cache
67            WHERE key=?
68        """, (key,))
69        self.con.commit()
70       
71    def keys(self):
72        cur = self.con.execute("""
73            SELECT key
74            FROM cache
75        """)
76        return [str(r[0]) for r in cur.fetchall()]
77       
78    def close(self):
79        pass
80   
81   
82class DictStore(Store, UserDict.DictMixin):
83    def __init__(self):
84        Store.__init__(self)
85       
86    def close(self):
87        pass
88   
89   
90from functools import wraps
91from contextlib import closing
92
93
94class cache_entry(object):
95    def __init__(self, value, mtime=None, expires=None):
96        self.value = value
97        self.mtime = mtime
98        self.expires = expires
99       
100_SESSION_START = datetime.now()
101
102class cached_wrapper(object):
103    """ TODO: needs documentation
104    """
105    def __init__(self, function, instance, class_, cache_store, last_modified=None):
106        self.function = function
107        self.instance = instance
108        self.class_ = class_
109        self.cache_store = cache_store
110        self.last_modified = last_modified
111       
112    def has_key(self, key):
113        with closing(self.cache_store()) as store:
114            return key in store
115   
116    def key_from_args(self, args, kwargs=None):
117        key = self.function.__name__ + repr(args)
118        return key
119   
120    def invalidate_key(self, key):
121        with closing(self.cache_store()) as store:
122            del store[key]
123           
124    def last_modified_from_args(self, args, kwargs=None):
125        key = self.key_from_args(args, kwargs)
126        if self.instance is not None:
127            self.instance.last_modified(args)
128       
129    def invalidate_args(self, args):
130        return self.invalidate_key(self.key_from_args(args))
131       
132    def invalidate_all(self):
133        prefix = self.key_from_args(()).rstrip(",)")
134        with self.cache_store() as store:
135            for key in store.keys():
136                if key.startswith(prefix):
137                    del store[key]
138   
139    def memoize(self, args, kwargs, value, timestamp=None):
140        key = self.key_from_args(args, kwargs)
141        if timestamp is None:
142            timestamp = datetime.now()
143           
144        with closing(self.cache_store()) as store:
145            store[key] = cache_entry(value, mtime=timestamp)
146       
147    def __call__(self, *args):
148        key = self.key_from_args(args)
149        with closing(self.cache_store()) as store:
150            valid = True
151            if key not in store:
152                valid = False
153            else:
154                entry = store[key]
155                rval = entry.value
156               
157                if not self.is_entry_valid(entry, args):
158                    valid = False
159            if not valid:
160                rval = self.function(self.instance, *args)
161                store[key] = cache_entry(rval, datetime.now(), None)
162       
163        return rval
164       
165    def min_timestamp(self, args):
166        key = self.key_from_args(args)
167        return datetime.fromtimestamp(0)
168   
169    def is_entry_valid(self, entry, args):
170        # Need to check datetime first (it subclasses date)
171        if isinstance(entry.mtime, datetime):
172            mtime = entry.mtime
173        elif isinstance(entry.mtime, date):
174            mtime = datetime(entry.mtime.year, entry.mtime.month,
175                             entry.mtime.day, 1, 1, 1)
176        else:
177            return False
178       
179        if self.min_timestamp(args) > mtime:
180            return False
181       
182        last_modified = self.last_modified_from_args(args)
183       
184        if isinstance(last_modified, date):
185            last_modified = datetime(last_modified.year, last_modified.month,
186                                     last_modified.day, 1, 1, 1)
187        elif isinstance(last_modified, basestring):
188            # Could have different format
189            mtime = mtime.strftime("%Y %m %d %H %M %S") 
190       
191        elif last_modified is None:
192            if conf.params["cache.invalidate"] == "always":
193                return False
194            elif conf.params["cache.invalidate"] == "session":
195                last_modified = _SESSION_START
196            elif conf.params["cache.invalidate"] == "daily":
197                last_modified = datetime.now().replace(hour=0, minute=0,
198                                                       second=0, microsecond=0)
199            elif conf.params["cache.invalidate"] == "weekly":
200                last_modified = datetime.now() - timedelta(7)
201            else: # ???
202                pass
203        return last_modified <= mtime
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   
240def clear_cache():
241    """Clear all locally cached KEGG data.
242    """
243    import glob
244    path = conf.params["cache.path"]
245    if os.path.realpath(path) != os.path.realpath(conf.kegg_dir):
246        raise Exception("Non default cache path. Please remove the contents of %r manually." % path)
247   
248    for cache_filename in glob.glob(os.path.join(path, "*.sqlite3")):
249        os.remove(cache_filename)
250       
251    for ko_filename in glob.glob(os.path.join(path, "*.keg")):
252        os.remove(ko_filename)
253       
254    for kgml_filename in glob.glob(os.path.join(path, "*.xml")):
255        os.remove(kgml_filename)
256       
257    for png_filename in glob.glob(os.path.join(path, "*.png")):
258        os.remove(png_filename)
259   
Note: See TracBrowser for help on using the repository browser.