1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import logging, md5, os, mimetools, urllib
19 from twisted.internet import defer
20 from twisted.python.failure import Failure
21 from twisted.web import client
22 try:
23 from xml.etree import ElementTree
24 except ImportError:
25 from elementtree import ElementTree
26
29 Exception.__init__(self)
30 self.code = int(code)
31 self.message = message
32
34 return "%d: %s" % (self.code, self.message)
35
36 (SIZE_SQUARE,
37 SIZE_THUMB,
38 SIZE_SMALL,
39 SIZE_MEDIUM,
40 SIZE_LARGE) = range (0, 5)
41
43 endpoint = "http://api.flickr.com/services/rest/?"
44
45 - def __init__(self, api_key, secret, perms="read"):
46 self.__methods = {}
47 self.api_key = api_key
48 self.secret = secret
49 self.perms = perms
50 self.token = None
51 self.logger = logging.getLogger('flickrest')
52
55
57 """Get the filename that contains the authentication token for the API key"""
58 return os.path.expanduser(os.path.join("~", ".flickr", self.api_key, "auth.xml"))
59
61 """Remove any cached information on disk."""
62 token = self.__getTokenFile()
63 if os.path.exists(token):
64 os.remove(token)
65
67 kwargs['api_key'] = self.api_key
68
69 if self.token:
70 kwargs['auth_token'] = self.token
71
72
73 sig = reduce(lambda sig, key: sig + key + str(kwargs[key]),
74 sorted(kwargs.keys()),
75 self.secret)
76 kwargs['api_sig'] = md5.new(sig).hexdigest()
77
78 - def __call(self, method, kwargs):
79 kwargs["method"] = method
80 self.__sign(kwargs)
81 self.logger.info("Calling %s" % method)
82 return client.getPage(Flickr.endpoint, method="POST",
83 headers={"Content-Type": "application/x-www-form-urlencoded"},
84 postdata=urllib.urlencode(kwargs))
85
86 - def __cb(self, data, method):
87 self.logger.info("%s returned" % method)
88 xml = ElementTree.XML(data)
89 if xml.tag == "rsp" and xml.get("stat") == "ok":
90 return xml
91 elif xml.tag == "rsp" and xml.get("stat") == "fail":
92 err = xml.find("err")
93 raise FlickrError(err.get("code"), err.get("msg"))
94 else:
95
96 raise FlickrError(0, "Invalid response")
97
99 method = "flickr." + method.replace("_", ".")
100 if not self.__methods.has_key(method):
101 def proxy(method=method, **kwargs):
102 return self.__call(method, kwargs).addCallback(self.__cb, method)
103 self.__methods[method] = proxy
104 return self.__methods[method]
105
106 @staticmethod
135
136
137 - def upload(self, filename=None, imageData=None, title=None, desc=None, tags=None):
138
139 if filename is None and imageData is None:
140 raise ValueError("Need to pass either filename or imageData")
141 if filename and imageData:
142 raise ValueError("Cannot pass both filename and imageData")
143
144 kwargs = {}
145 if title:
146 kwargs['title'] = title
147 if desc:
148 kwargs['description'] = desc
149 if tags:
150 kwargs['tags'] = tags
151 self.__sign(kwargs)
152
153 if imageData:
154 kwargs['photo'] = imageData
155 else:
156 kwargs['photo'] = file(filename, "rb")
157
158 (boundary, form) = self.__encodeForm(kwargs)
159 headers= {
160 "Content-Type": "multipart/form-data; boundary=%s" % boundary,
161 "Content-Length": str(len(form))
162 }
163
164 self.logger.info("Calling upload")
165 return client.getPage("http://api.flickr.com/services/upload/", method="POST",
166 headers=headers, postdata=form).addCallback(self.__cb, "upload")
167
182 return self.auth_getToken(frob=state['frob']).addCallback(gotToken)
183
185 """Attempts to log in to Flickr. The return value is a Twisted Deferred
186 object that callbacks when the first part of the authentication is
187 completed. If the result passed to the deferred callback is None, then
188 the required authentication was locally cached and you are
189 authenticated. Otherwise the result is a dictionary, you should open
190 the URL specified by the 'url' key and instruct the user to follow the
191 instructions. Once that is done, pass the state to
192 flickrest.authenticate_2()."""
193
194 filename = self.__getTokenFile()
195 if os.path.exists(filename):
196 try:
197 e = ElementTree.parse(filename).getroot()
198 self.token = e.find("auth/token").text
199 return defer.succeed(None)
200 except:
201
202 pass
203
204 def gotFrob(xml):
205 frob = xml.find("frob").text
206 keys = { 'perms': self.perms,
207 'frob': frob }
208 self.__sign(keys)
209 url = "http://flickr.com/services/auth/?api_key=%(api_key)s&perms=%(perms)s&frob=%(frob)s&api_sig=%(api_sig)s" % keys
210 return {'url': url, 'frob': frob}
211 return self.auth_getFrob().addCallback(gotFrob)
212
213 @staticmethod
215 if photo is None:
216 return None
217
218
219 suffix = ""
220 if size == SIZE_SQUARE:
221 suffix = "_s"
222 elif size == SIZE_THUMB:
223 suffix = "_t"
224 elif size == SIZE_SMALL:
225 suffix = "_m"
226 elif size == SIZE_LARGE:
227 suffix = "_b"
228
229 return "http://static.flickr.com/%s/%s_%s%s.jpg" % (photo.get("server"), photo.get("id"), photo.get("secret"), suffix)
230