source: orange-bioinformatics/orangecontrib/bio/obiKEGG/caching.py @ 1873:0810c5708cc5

Revision 1873:0810c5708cc5, 7.6 KB checked in by Ales Erjavec <ales.erjavec@…>, 6 months ago (diff)

Moved '_bioinformatics' into orangecontrib namespace.

Line 
1"""
2Caching framework for cached kegg api calls.
3
4"""
5import os
6import UserDict
7import sqlite3
8import cPickle as pickle
9
10from contextlib import closing
11
12from datetime import datetime, date, timedelta
13from . import conf
14
15
16class Store(object):
17    def __init__(self):
18        self.timestamp = 0
19
20    def open(self):
21        raise NotImplementedError
22
23    def __enter__(self):
24        return self
25
26    def __exit__(self, *args):
27        pass
28
29
30class Sqlite3Store(Store, UserDict.DictMixin):
31    def __init__(self, filename):
32        self.filename = filename
33        self.con = sqlite3.connect(filename)
34        self.con.execute("""
35            CREATE TABLE IF NOT EXISTS cache
36                (key TEXT UNIQUE,
37                 value TEXT
38                )
39        """)
40        self.con.execute("""
41            CREATE INDEX IF NOT EXISTS cache_index
42            ON cache (key)
43        """)
44        self.con.commit()
45
46    def __getitem__(self, key):
47        cur = self.con.execute("""
48            SELECT value
49            FROM cache
50            WHERE key=?
51        """, (key,))
52        r = cur.fetchall()
53
54        if not r:
55            raise KeyError(key)
56        else:
57            return pickle.loads(str(r[0][0]))
58
59    def __setitem__(self, key, value):
60        value = pickle.dumps(value)
61        self.con.execute("""
62            INSERT OR REPLACE INTO cache
63            VALUES (?, ?)
64        """, (key, value))
65        self.con.commit()
66
67    def __delitem__(self, key):
68        self.con.execute("""
69            DELETE FROM cache
70            WHERE key=?
71        """, (key,))
72        self.con.commit()
73
74    def keys(self):
75        cur = self.con.execute("""
76            SELECT key
77            FROM cache
78        """)
79        return [str(r[0]) for r in cur.fetchall()]
80
81    def close(self):
82        pass
83
84
85class DictStore(Store, UserDict.DictMixin):
86    def __init__(self):
87        Store.__init__(self)
88
89    def close(self):
90        pass
91
92
93class cache_entry(object):
94    def __init__(self, value, mtime=None, expires=None):
95        self.value = value
96        self.mtime = mtime
97        self.expires = expires
98
99_SESSION_START = datetime.now()
100
101
102class cached_wrapper(object):
103    """
104    TODO: needs documentation
105    """
106    def __init__(self, function, instance, class_, cache_store,
107                 last_modified=None):
108        self.function = function
109        self.instance = instance
110        self.class_ = class_
111        self.cache_store = cache_store
112        self.last_modified = last_modified
113
114    def has_key(self, key):
115        with closing(self.cache_store()) as store:
116            return key in store
117
118    def key_from_args(self, args, kwargs=None):
119        key = self.function.__name__ + repr(args)
120        if kwargs is not None:
121            raise NotImplementedError("kwargs are not supported yet")
122        return key
123
124    def invalidate_key(self, key):
125        with closing(self.cache_store()) as store:
126            del store[key]
127
128    def last_modified_from_args(self, args, kwargs=None):
129        if self.instance is not None:
130            return self.instance.last_modified(args)
131
132    def invalidate_args(self, args):
133        return self.invalidate_key(self.key_from_args(args))
134
135    def invalidate_all(self):
136        prefix = self.key_from_args(()).rstrip(",)")
137        with self.cache_store() as store:
138            for key in store.keys():
139                if key.startswith(prefix):
140                    del store[key]
141
142    def memoize(self, args, kwargs, value, timestamp=None):
143        key = self.key_from_args(args, kwargs)
144        if timestamp is None:
145            timestamp = datetime.now()
146
147        with closing(self.cache_store()) as store:
148            store[key] = cache_entry(value, mtime=timestamp)
149
150    def __call__(self, *args):
151        key = self.key_from_args(args)
152        with closing(self.cache_store()) as store:
153            if self.key_has_valid_cache(key, store):
154                rval = store[key].value
155            else:
156                rval = self.function(self.instance, *args)
157                store[key] = cache_entry(rval, datetime.now(), None)
158
159        return rval
160
161    def key_has_valid_cache(self, key, store):
162        if key not in store:
163            return False
164        else:
165            entry = store[key]
166            return self.is_entry_valid(entry, None)
167
168    def is_entry_valid(self, entry, args):
169        # For now always return True, the caching architecture needs to be
170        # completely reworked
171        return True
172
173        # Need to check datetime first (it subclasses date)
174        if isinstance(entry.mtime, datetime):
175            mtime = entry.mtime
176        elif isinstance(entry.mtime, date):
177            mtime = datetime(entry.mtime.year, entry.mtime.month,
178                             entry.mtime.day, 1, 1, 1)
179        else:
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
205
206class cached_method(object):
207    def __init__(self, function):
208        self.function = function
209
210    def __get__(self, instance, owner):
211        if instance is not None:
212            return cached_wrapper(self.function, instance, owner,
213                                  self.get_cache_store(instance, owner))
214        return self
215
216    def get_cache_store(self, instance, owner):
217        if hasattr(instance, "cache_store"):
218            return instance.cache_store
219        elif not hasattr("_cached_method_cache"):
220            instance._cached_method_cache = DictStore()
221        return instance._cached_method_cache
222
223
224class bget_cached_method(cached_method):
225    def __get__(self, instance, owner):
226        if instance is not None:
227            return cached_wrapper(self.function, instance, owner,
228                                  self.get_cache_store(instance, owner),
229                                  self.get_last_modified(instance, owner))
230        return self
231
232    def get_last_modified(self, instance, owner):
233        if hasattr(instance, "last_modified"):
234            return instance.last_modified
235
236
237def touch_dir(path):
238    path = os.path.expanduser(path)
239    if not os.path.exists(path):
240        os.makedirs(path)
241
242
243def clear_cache():
244    """Clear all locally cached KEGG data.
245    """
246    import glob
247    path = conf.params["cache.path"]
248    if os.path.realpath(path) != os.path.realpath(conf.kegg_dir):
249        raise Exception("Non default cache path. Please remove the contents "
250                        "of %r manually." % path)
251
252    for cache_filename in glob.glob(os.path.join(path, "*.sqlite3")):
253        os.remove(cache_filename)
254
255    for ko_filename in glob.glob(os.path.join(path, "*.keg")):
256        os.remove(ko_filename)
257
258    for kgml_filename in glob.glob(os.path.join(path, "*.xml")):
259        os.remove(kgml_filename)
260
261    for png_filename in glob.glob(os.path.join(path, "*.png")):
262        os.remove(png_filename)
Note: See TracBrowser for help on using the repository browser.