Package flickyou :: Package backend :: Module base

Source Code for Module flickyou.backend.base

  1  import mimetools 
  2  import os 
  3  import time 
  4  import urllib 
  5  import urllib2 
  6  from xml.dom import minidom 
  7   
  8  import utils 
  9   
10 -class BackendError(Exception):
11 """This exception class represents any kind of error raised by the 12 Flickr API.""" 13 pass
14
15 -class BaseBackend(object):
16 """The abstract class representing the machinery of the request/response 17 formats. 18 19 Any implementor *must* override: 20 * response_format 21 * _get() 22 * _get_backend_params() 23 * _handle_error() 24 * _parse_response() 25 26 * auth_checkToken() 27 * auth_getToken() 28 * auth_getFrob() 29 30 For the authentication step also "authenticate_step_one()" can be overridden. 31 32 This backend uses REST as a Request format."""
33 - def __init__(self, api_key, secret_key):
34 self.api_key = api_key 35 self.secret_key = secret_key 36 self.token = None 37 self.frob = None 38 # This is the string representing the HTTP response format 39 # It can be "json", "rest", "soap", "xmlrpc", "php_serial" 40 # See the Flickr API documentation for details: <http://www.flickr.com/services/api/> 41 self.response_format = None # override this in the backends 42 self.cache = utils.TokenCache(self.api_key)
43
44 - def _get(self, method, auth, **params):
45 """Retrieves the data from the Flickr server. 46 47 * method: the name of the remote method to invoke. 48 * auth: True if the call has to be authenticated. False otherwise. 49 * params: any argument for the call.""" 50 raise NotImplementedError
51
52 - def _get_backend_params(self):
53 """Used to setup specific backend parameters for the HTTP request. 54 55 To be overridden. 56 57 Must return a dict() of params.""" 58 raise NotImplementedError
59
60 - def _handle_error(self, response):
61 """Here goes all the error handling for the response. 62 63 To be overridden. 64 65 It should raise BackendError extracting the proper message data 66 from the response. 67 The response format is already converted by _parse_response.""" 68 raise NotImplementedError
69
70 - def _parse_response(self, response):
71 """Should convert the response into valid Python data types. 72 73 To be overridden.""" 74 raise NotImplementedError
75
76 - def call_api_method(self, api_method_name, auth, **params):
77 """Calls the remote API method. 78 79 * api_method_name is the Flickr name (eg. flickr.photos.getInfo) 80 * auth is a boolean indicating whether or not the call requires authentication 81 * params contains any additional parameter""" 82 response = self._get(api_method_name, auth, **params) 83 response = self._parse_response(response) 84 self._handle_error(response) 85 return response
86
87 - def authenticate(self, requested_perms='write'):
88 """All the authentication machinery. 89 90 * requested_perms can be 'read', 'write', 'delete' 91 92 See: <http://www.flickr.com/services/api/auth.spec.html> 93 94 Takes also care of storing the token on the file system.""" 95 self.token = self.token or self.cache.get() 96 97 if self.token: 98 self.token, perms = self.auth_checkToken() 99 100 # if permissions don't match we need a new token 101 if perms == 'read' and requested_perms in ('write', 'delete'): 102 self.token = None 103 if perms == 'write' and requested_perms == 'delete': 104 self.token = None 105 106 if not self.token: 107 self.authenticate_step_one(requested_perms) 108 self.token = self.auth_getToken() 109 self.cache.put(self.token)
110 111
112 - def authenticate_step_one(self, perms='write', sleep_time=3):
113 """ This implement the first step of authentication machinery. 114 115 Note: override this to do your first step. By default uses the system 116 browser to authenticate and desktop authentication.""" 117 118 frob = self.auth_getFrob() 119 auth_url = self._get_authentication_url(perms, frob) 120 utils.open_url_in_browser(auth_url) 121 time.sleep(sleep_time) # needed to authenticate
122
123 - def _get_authentication_url(self, perms, frob):
124 d = dict(api_key=self.api_key, frob=frob, perms=perms) 125 d['api_sig'] = utils.get_api_signature(self.secret_key, **d) 126 auth_url = "%s?%s" % (utils.AUTH_API_URL, urllib.urlencode(d)) 127 return auth_url
128
129 - def auth_checkToken(self):
130 """flickr.auth.checkToken: 131 Returns the credentials attached to an authentication token. 132 This method call must be signed. 133 134 To be overridden. 135 136 See: <http://www.flickr.com/services/api/flickr.auth.checkToken.html>""" 137 raise NotImplementedError
138
139 - def auth_getFrob(self):
140 """flickr.auth.getFrob: 141 Returns a frob to be used during authentication. 142 This method call must be signed. 143 144 To be overridden. 145 146 See: <http://www.flickr.com/services/api/flickr.auth.getFrob.html>""" 147 raise NotImplementedError
148
149 - def auth_getToken(self):
150 """flickr.auth.getToken: 151 Returns the auth token for the given frob, if one has been attached. 152 This method call must be signed. 153 154 To be overridden. 155 156 See: <http://www.flickr.com/services/api/flickr.auth.getToken.html>""" 157 raise NotImplementedError
158
159 - def photo_upload(self, filename=None, photo_data=None, 160 data_content_type='image/jpeg', **params):
161 """Uploading photos: 162 It works outside the normal Flickr API framework because it 163 involves sending binary files over the wire. 164 165 You can specify either the filename or pass the binary image data. 166 The image content type defaults to JPEG. 167 168 See: <http://www.flickr.com/services/api/upload.api.html> 169 """ 170 params['api_key'] = self.api_key 171 params['auth_token'] = self.token 172 params['api_sig'] = utils.get_api_signature(self.secret_key, **params) 173 174 # build data to POST 175 # see: http://www.flickr.com/services/api/upload.example.html 176 boundary = mimetools.choose_boundary() 177 request_body = [] 178 179 # required params 180 for field in ('api_key', 'auth_token', 'api_sig'): 181 request_body.append("--%s\r\n" % boundary) 182 request_body.append( 183 'Content-Disposition: form-data; name="%s"\r\n\r\n' % field) 184 request_body.append("%s\r\n" % params[field]) 185 186 # optional params 187 for field in ('title', 'description', 'tags', 'is_public', 'is_friend', 188 'is_family', 'safety_level', 'content_type', 'hidden'): 189 if field in params: 190 request_body.append("--%s\r\n" % boundary) 191 request_body.append( 192 'Content-Disposition: form-data; name="%s"\r\n\r\n' % field) 193 request_body.append("%s\r\n" % params[field]) 194 195 # photo field 196 request_body.append("--%s\r\n" % boundary) 197 request_body.append( 198 'Content-Disposition: form-data;' 199 'name="photo"; filename="%s"\r\n' % filename) 200 request_body.append( 201 'Content-Type: %s\r\n\r\n' % data_content_type or 'image/jpeg') 202 203 if filename and photo_data is None: 204 path = os.path.abspath(os.path.normpath(filename)) 205 f = open(path, "rb") 206 photo_data = f.read() 207 f.close() 208 209 request_data = [] 210 request_data.append("".join(request_body).encode('utf-8')) 211 request_data.append(photo_data) 212 request_data.append(('\r\n--%s--' % boundary).encode('utf-8')) 213 request_data = "".join(request_data) 214 215 request = urllib2.Request(utils.UPLOAD_URL) 216 request.add_data(request_data) 217 request.add_header( 218 "Content-Type", "multipart/form-data; boundary=%s" % boundary) 219 220 response = urllib2.urlopen(request) 221 photo_data = response.read() 222 223 def _handle_error(photo_data): 224 dom = minidom.parseString(photo_data) 225 rsp = dom.getElementsByTagName('rsp')[0] 226 if rsp.getAttribute('stat') == 'fail': 227 err = rsp.getElementsByTagName('err')[0] 228 code = err.getAttribute('code') 229 message = err.getAttribute('msg') 230 value = "%s: %s" % (code, message) 231 raise BackendError(value)
232 233 def _get_photo_id(photo_data): 234 dom = minidom.parseString(photo_data) 235 photo_id_tag = dom.getElementsByTagName('photoid')[0] 236 return photo_id_tag.firstChild.nodeValue
237 238 _handle_error(photo_data) 239 photo_id = _get_photo_id(photo_data) 240 return photo_id 241