2 # Copyright 2009-2010 Joshua Roesslein
3 # See LICENSE for details.
11 import cPickle as pickle
24 # Probably on a windows system
32 def __init__(self, timeout=60):
33 """Initialize the cache
34 timeout: number of seconds to keep a cached entry
36 self.timeout = timeout
38 def store(self, key, value):
39 """Add new record to cache
43 raise NotImplementedError
45 def get(self, key, timeout=None):
46 """Get cached entry if exists and not expired
47 key: which entry to get
48 timeout: override timeout with this value [optional]
50 raise NotImplementedError
53 """Get count of entries currently stored in cache"""
54 raise NotImplementedError
57 """Delete any expired entries in cache."""
58 raise NotImplementedError
61 """Delete all cached entries"""
62 raise NotImplementedError
65 class MemoryCache(Cache):
68 def __init__(self, timeout=60):
69 Cache.__init__(self, timeout)
71 self.lock = threading.Lock()
73 def __getstate__(self):
75 return {'entries': self._entries, 'timeout': self.timeout}
77 def __setstate__(self, state):
79 self.lock = threading.Lock()
80 self._entries = state['entries']
81 self.timeout = state['timeout']
83 def _is_expired(self, entry, timeout):
84 return timeout > 0 and (time.time() - entry[0]) >= timeout
86 def store(self, key, value):
88 self._entries[key] = (time.time(), value)
91 def get(self, key, timeout=None):
94 # check to see if we have this key
95 entry = self._entries.get(key)
97 # no hit, return nothing
100 # use provided timeout in arguments if provided
101 # otherwise use the one provided during init.
103 timeout = self.timeout
105 # make sure entry is not expired
106 if self._is_expired(entry, timeout):
107 # entry expired, delete and return nothing
108 del self._entries[key]
111 # entry found and not expired, return it
117 return len(self._entries)
122 for k, v in self._entries.items():
123 if self._is_expired(v, self.timeout):
130 self._entries.clear()
134 class FileCache(Cache):
135 """File-based cache"""
137 # locks used to make cache thread-safe
140 def __init__(self, cache_dir, timeout=60):
141 Cache.__init__(self, timeout)
142 if os.path.exists(cache_dir) is False:
144 self.cache_dir = cache_dir
145 if cache_dir in FileCache.cache_locks:
146 self.lock = FileCache.cache_locks[cache_dir]
148 self.lock = threading.Lock()
149 FileCache.cache_locks[cache_dir] = self.lock
151 if os.name == 'posix':
152 self._lock_file = self._lock_file_posix
153 self._unlock_file = self._unlock_file_posix
154 elif os.name == 'nt':
155 self._lock_file = self._lock_file_win32
156 self._unlock_file = self._unlock_file_win32
158 print('Warning! FileCache locking not supported on this system!')
159 self._lock_file = self._lock_file_dummy
160 self._unlock_file = self._unlock_file_dummy
162 def _get_path(self, key):
165 return os.path.join(self.cache_dir, md5.hexdigest())
167 def _lock_file_dummy(self, path, exclusive=True):
170 def _unlock_file_dummy(self, lock):
173 def _lock_file_posix(self, path, exclusive=True):
174 lock_path = path + '.lock'
175 if exclusive is True:
176 f_lock = open(lock_path, 'w')
177 fcntl.lockf(f_lock, fcntl.LOCK_EX)
179 f_lock = open(lock_path, 'r')
180 fcntl.lockf(f_lock, fcntl.LOCK_SH)
181 if os.path.exists(lock_path) is False:
186 def _unlock_file_posix(self, lock):
189 def _lock_file_win32(self, path, exclusive=True):
193 def _unlock_file_win32(self, lock):
197 def _delete_file(self, path):
199 if os.path.exists(path + '.lock'):
200 os.remove(path + '.lock')
202 def store(self, key, value):
203 path = self._get_path(key)
206 # acquire lock and open file
207 f_lock = self._lock_file(path)
208 datafile = open(path, 'wb')
211 pickle.dump((time.time(), value), datafile)
213 # close and unlock file
215 self._unlock_file(f_lock)
219 def get(self, key, timeout=None):
220 return self._get(self._get_path(key), timeout)
222 def _get(self, path, timeout):
223 if os.path.exists(path) is False:
228 # acquire lock and open
229 f_lock = self._lock_file(path, False)
230 datafile = open(path, 'rb')
232 # read pickled object
233 created_time, value = pickle.load(datafile)
236 # check if value is expired
238 timeout = self.timeout
239 if timeout > 0 and (time.time() - created_time) >= timeout:
240 # expired! delete from cache
242 self._delete_file(path)
244 # unlock and return result
245 self._unlock_file(f_lock)
252 for entry in os.listdir(self.cache_dir):
253 if entry.endswith('.lock'):
259 for entry in os.listdir(self.cache_dir):
260 if entry.endswith('.lock'):
262 self._get(os.path.join(self.cache_dir, entry), None)
265 for entry in os.listdir(self.cache_dir):
266 if entry.endswith('.lock'):
268 self._delete_file(os.path.join(self.cache_dir, entry))
270 class MemCacheCache(Cache):
271 """Cache interface"""
273 def __init__(self, client, timeout=60):
274 """Initialize the cache
275 client: The memcache client
276 timeout: number of seconds to keep a cached entry
279 self.timeout = timeout
281 def store(self, key, value):
282 """Add new record to cache
286 self.client.set(key, value, time=self.timeout)
288 def get(self, key, timeout=None):
289 """Get cached entry if exists and not expired
290 key: which entry to get
291 timeout: override timeout with this value [optional]. DOES NOT WORK HERE
293 return self.client.get(key)
296 """Get count of entries currently stored in cache. RETURN 0"""
297 raise NotImplementedError
300 """Delete any expired entries in cache. NO-OP"""
301 raise NotImplementedError
304 """Delete all cached entries. NO-OP"""
305 raise NotImplementedError
307 class RedisCache(Cache):
308 '''Cache running in a redis server'''
310 def __init__(self, client, timeout=60, keys_container = 'tweepy:keys', pre_identifier = 'tweepy:'):
311 Cache.__init__(self, timeout)
313 self.keys_container = keys_container
314 self.pre_identifier = pre_identifier
316 def _is_expired(self, entry, timeout):
317 # Returns true if the entry has expired
318 return timeout > 0 and (time.time() - entry[0]) >= timeout
320 def store(self, key, value):
321 '''Store the key, value pair in our redis server'''
322 # Prepend tweepy to our key, this makes it easier to identify tweepy keys in our redis server
323 key = self.pre_identifier + key
324 # Get a pipe (to execute several redis commands in one step)
325 pipe = self.client.pipeline()
326 # Set our values in a redis hash (similar to python dict)
327 pipe.set(key, pickle.dumps((time.time(), value)))
329 pipe.expire(key, self.timeout)
330 # Add the key to a set containing all the keys
331 pipe.sadd(self.keys_container, key)
332 # Execute the instructions in the redis server
335 def get(self, key, timeout=None):
336 '''Given a key, returns an element from the redis table'''
337 key = self.pre_identifier + key
338 # Check to see if we have this key
339 unpickled_entry = self.client.get(key)
340 if not unpickled_entry:
341 # No hit, return nothing
344 entry = pickle.loads(unpickled_entry)
345 # Use provided timeout in arguments if provided
346 # otherwise use the one provided during init.
348 timeout = self.timeout
350 # Make sure entry is not expired
351 if self._is_expired(entry, timeout):
352 # entry expired, delete and return nothing
353 self.delete_entry(key)
355 # entry found and not expired, return it
359 '''Note: This is not very efficient, since it retreives all the keys from the redis
360 server to know how many keys we have'''
361 return len(self.client.smembers(self.keys_container))
363 def delete_entry(self, key):
364 '''Delete an object from the redis table'''
365 pipe = self.client.pipeline()
366 pipe.srem(self.keys_container, key)
371 '''Cleanup all the expired keys'''
372 keys = self.client.smembers(self.keys_container)
374 entry = self.client.get(key)
376 entry = pickle.loads(entry)
377 if self._is_expired(entry, self.timeout):
378 self.delete_entry(key)
381 '''Delete all entries from the cache'''
382 keys = self.client.smembers(self.keys_container)
384 self.delete_entry(key)
387 class MongodbCache(Cache):
388 """A simple pickle-based MongoDB cache sytem."""
390 def __init__(self, db, timeout=3600, collection='tweepy_cache'):
391 """Should receive a "database" cursor from pymongo."""
392 Cache.__init__(self, timeout)
393 self.timeout = timeout
394 self.col = db[collection]
395 self.col.create_index('created', expireAfterSeconds=timeout)
397 def store(self, key, value):
398 from bson.binary import Binary
400 now = datetime.datetime.utcnow()
401 blob = Binary(pickle.dumps(value))
403 self.col.insert({'created': now, '_id': key, 'value': blob})
405 def get(self, key, timeout=None):
407 raise NotImplementedError
408 obj = self.col.find_one({'_id': key})
410 return pickle.loads(obj['value'])
413 return self.col.find({}).count()
415 def delete_entry(self, key):
416 return self.col.remove({'_id': key})
419 """MongoDB will automatically clear expired keys."""
424 self.col.create_index('created', expireAfterSeconds=self.timeout)