source: orange-bioinformatics/_bioinformatics/obiKEGG2/caching.py @ 1636:10d234fdadb9

Revision 1636:10d234fdadb9, 7.9 KB checked in by mitar, 2 years ago (diff)

Restructuring because we will not be using namespaces.

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.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.