Package flickrapi :: Module tokencache
[hide private]
[frames] | no frames]

Source Code for Module flickrapi.tokencache

  1   
  2  '''Persistent token cache management for the Flickr API''' 
  3   
  4  import os.path 
  5  import logging 
  6  import time 
  7   
  8  from flickrapi.exceptions import LockingError 
  9   
 10  logging.basicConfig() 
 11  LOG = logging.getLogger(__name__) 
 12  LOG.setLevel(logging.INFO) 
 13   
 14  __all__ = ('TokenCache', 'SimpleTokenCache') 
15 16 17 -class SimpleTokenCache(object):
18 '''In-memory token cache.''' 19
20 - def __init__(self):
21 self.token = None
22
23 - def forget(self):
24 '''Removes the cached token''' 25 26 self.token = None
27
28 29 -class TokenCache(object):
30 '''On-disk persistent token cache for a single application. 31 32 The application is identified by the API key used. Per 33 application multiple users are supported, with a single 34 token per user. 35 ''' 36
37 - def __init__(self, api_key, username=None):
38 '''Creates a new token cache instance''' 39 40 self.api_key = api_key 41 self.username = username 42 self.memory = {} 43 self.path = os.path.join("~", ".flickr")
44
45 - def get_cached_token_path(self):
46 """Return the directory holding the app data.""" 47 return os.path.expanduser(os.path.join(self.path, self.api_key))
48
50 """Return the full pathname of the cached token file.""" 51 52 if self.username: 53 filename = 'auth-%s.token' % self.username 54 else: 55 filename = 'auth.token' 56 57 return os.path.join(self.get_cached_token_path(), filename)
58
59 - def get_cached_token(self):
60 """Read and return a cached token, or None if not found. 61 62 The token is read from the cached token file. 63 """ 64 65 # Only read the token once 66 if self.username in self.memory: 67 return self.memory[self.username] 68 69 try: 70 f = open(self.get_cached_token_filename(), "r") 71 token = f.read() 72 f.close() 73 74 return token.strip() 75 except IOError: 76 return None
77
78 - def set_cached_token(self, token):
79 """Cache a token for later use.""" 80 81 # Remember for later use 82 self.memory[self.username] = token 83 84 path = self.get_cached_token_path() 85 if not os.path.exists(path): 86 os.makedirs(path) 87 88 f = open(self.get_cached_token_filename(), "w") 89 f.write(token) 90 f.close()
91
92 - def forget(self):
93 '''Removes the cached token''' 94 95 if self.username in self.memory: 96 del self.memory[self.username] 97 filename = self.get_cached_token_filename() 98 if os.path.exists(filename): 99 os.unlink(filename)
100 101 token = property(get_cached_token, set_cached_token, 102 forget, "The cached token")
103
104 105 -class LockingTokenCache(TokenCache):
106 '''Locks the token cache when reading or updating it, so that 107 multiple processes can safely use the same API key. 108 ''' 109
110 - def get_lock_name(self):
111 '''Returns the filename of the lock.''' 112 113 token_name = self.get_cached_token_filename() 114 return '%s-lock' % token_name
115 lock = property(get_lock_name) 116
117 - def get_pidfile_name(self):
118 '''Returns the name of the pidfile in the lock directory.''' 119 120 return os.path.join(self.lock, 'pid')
121 pidfile_name = property(get_pidfile_name) 122
123 - def get_lock_pid(self):
124 '''Returns the PID that is stored in the lock directory, or 125 None if there is no such file. 126 ''' 127 128 filename = self.pidfile_name 129 if not os.path.exists(filename): 130 return None 131 132 pidfile = open(filename) 133 try: 134 pid = pidfile.read() 135 if pid: 136 return int(pid) 137 finally: 138 pidfile.close() 139 140 return None
141
142 - def acquire(self, timeout=60):
143 '''Locks the token cache for this key and username. 144 145 If the token cache is already locked, waits until it is 146 released. Throws an exception when the lock cannot be acquired 147 after ``timeout`` seconds. 148 ''' 149 150 # Check whether there is a PID file already with our PID in 151 # it. 152 lockpid = self.get_lock_pid() 153 if lockpid == os.getpid(): 154 LOG.debug('The lock is ours, continuing') 155 return 156 157 # Figure out the lock filename 158 lock = self.get_lock_name() 159 LOG.debug('Acquiring lock %s' % lock) 160 161 # Try to obtain the lock 162 start_time = time.time() 163 while True: 164 try: 165 os.makedirs(lock) 166 break 167 except OSError: 168 # If the path doesn't exist, the error isn't that it 169 # can't be created because someone else has got the 170 # lock. Just bail out then. 171 if not os.path.exists(lock): 172 LOG.error('Unable to acquire lock %s, aborting' % 173 lock) 174 raise 175 176 if time.time() - start_time >= timeout: 177 # Timeout has passed, bail out 178 raise LockingError('Unable to acquire lock ' + 179 '%s, aborting' % lock) 180 181 # Wait for a bit, then try again 182 LOG.debug('Unable to acquire lock, waiting') 183 time.sleep(0.1) 184 185 # Write the PID file 186 LOG.debug('Lock acquired, writing our PID') 187 pidfile = open(self.pidfile_name, 'w') 188 try: 189 pidfile.write('%s' % os.getpid()) 190 finally: 191 pidfile.close()
192
193 - def release(self):
194 '''Unlocks the token cache for this key.''' 195 196 # Figure out the lock filename 197 lock = self.get_lock_name() 198 if not os.path.exists(lock): 199 LOG.warn('Trying to release non-existing lock %s' % lock) 200 return 201 202 # If the PID file isn't ours, abort. 203 lockpid = self.get_lock_pid() 204 if lockpid and lockpid != os.getpid(): 205 raise LockingError(('Lock %s is NOT ours, but belongs ' + 206 'to PID %i, unable to release.') % (lock, 207 lockpid)) 208 209 LOG.debug('Releasing lock %s' % lock) 210 211 # Remove the PID file and the lock directory 212 pidfile = self.pidfile_name 213 if os.path.exists(pidfile): 214 os.remove(pidfile) 215 os.removedirs(lock)
216
217 - def __del__(self):
218 '''Cleans up any existing lock.''' 219 220 # Figure out the lock filename 221 lock = self.get_lock_name() 222 if not os.path.exists(lock): 223 return 224 225 # If the PID file isn't ours, we're done 226 lockpid = self.get_lock_pid() 227 if lockpid and lockpid != os.getpid(): 228 return 229 230 # Release the lock 231 self.release()
232
233 - def locked(method):
234 '''Decorator, ensures the method runs in a locked cache.''' 235 236 def locker(self, *args, **kwargs): 237 self.acquire() 238 try: 239 return method(self, *args, **kwargs) 240 finally: 241 self.release()
242 243 return locker
244 245 @locked
246 - def get_cached_token(self):
247 """Read and return a cached token, or None if not found. 248 249 The token is read from the cached token file. 250 """ 251 252 return TokenCache.get_cached_token(self)
253 254 @locked
255 - def set_cached_token(self, token):
256 """Cache a token for later use.""" 257 258 TokenCache.set_cached_token(self, token)
259 260 @locked
261 - def forget(self):
262 '''Removes the cached token''' 263 264 TokenCache.forget(self)
265 266 token = property(get_cached_token, set_cached_token, 267 forget, "The cached token") 268