1
+ #!/usr/bin/env python
2
+
3
+ """ Python-Flickr """
4
+ '''
5
+ For Flickr API documentation, visit: http://www.flickr.com/services/api/
6
+ '''
7
+
8
+ __author__ = 'Mike Helmick <[email protected] >'
9
+ __version__ = '0.1.0'
10
+
11
+ import time
12
+ import urllib
13
+ import urllib2
14
+ import httplib2
15
+ import oauth2 as oauth
16
+
17
+ try :
18
+ from urlparse import parse_qsl
19
+ except ImportError :
20
+ from cgi import parse_qsl
21
+
22
+ try :
23
+ import simplejson as json
24
+ except ImportError :
25
+ try :
26
+ import json
27
+ except ImportError :
28
+ try :
29
+ from django .utils import simplejson as json
30
+ except ImportError :
31
+ raise ImportError ('A json library is required to use this python library. Lol, yay for being verbose. ;)' )
32
+
33
+ class FlickrAPIError (Exception ): pass
34
+ class FlickrAuthError (FlickrAPIError ): pass
35
+
36
+ class FlickrAPI (object ):
37
+ def __init__ (self , api_key = None , api_secret = None , oauth_token = None , oauth_token_secret = None , callback_url = None , headers = None , client_args = {}):
38
+ if not api_key or not api_secret :
39
+ raise FlickrAPIError ('Please supply an api_key and api_secret.' )
40
+
41
+ self .api_key = api_key
42
+ self .api_secret = api_secret
43
+ self .oauth_token = oauth_token
44
+ self .oauth_token_secret = oauth_token_secret
45
+ self .callback_url = callback_url
46
+
47
+ self .api_base = 'http://api.flickr.com/services'
48
+ self .rest_api_url = '%s/rest' % self .api_base
49
+ self .upload_api_url = '%s/upload/' % self .api_base
50
+ self .request_token_url = 'http://www.flickr.com/services/oauth/request_token'
51
+ self .access_token_url = 'http://www.flickr.com/services/oauth/access_token'
52
+ self .authorize_url = 'http://www.flickr.com/services/oauth/authorize'
53
+
54
+ self .default_params = {'api_key' :self .api_key }
55
+
56
+ self .headers = headers
57
+ if self .headers is None :
58
+ self .headers = {'User-agent' : 'Python-Flickr v1.0' }
59
+
60
+ self .consumer = None
61
+ self .token = None
62
+
63
+ if self .api_key is not None and self .api_secret is not None :
64
+ self .consumer = oauth .Consumer (self .api_key , self .api_secret )
65
+
66
+ if self .oauth_token is not None and self .oauth_token_secret is not None :
67
+ self .token = oauth .Token (oauth_token , oauth_token_secret )
68
+
69
+ # Filter down through the possibilities here - if they have a token, if they're first stage, etc.
70
+ if self .consumer is not None and self .token is not None :
71
+ self .client = oauth .Client (self .consumer , self .token , ** client_args )
72
+ elif self .consumer is not None :
73
+ self .client = oauth .Client (self .consumer , ** client_args )
74
+ else :
75
+ # If they don't do authentication, but still want to request unprotected resources, we need an opener.
76
+ self .client = httplib2 .Http (** client_args )
77
+
78
+ def get_authentication_tokens (self , perms = None ):
79
+ """ Returns an authorization url to give to your user.
80
+
81
+ Parameters:
82
+ perms - If None, this is ignored and uses your applications default perms. If set, will overwrite applications perms; acceptable perms (read, write, delete)
83
+ * read - permission to read private information
84
+ * write - permission to add, edit and delete photo metadata (includes 'read')
85
+ * delete - permission to delete photos (includes 'write' and 'read')
86
+ """
87
+
88
+ request_args = {}
89
+ resp , content = self .client .request ('%s?oauth_callback=%s' % (self .request_token_url , self .callback_url ), 'GET' , ** request_args )
90
+
91
+ if resp ['status' ] != '200' :
92
+ raise FlickrAuthError ('There was a problem retrieving an authentication url.' )
93
+
94
+ request_tokens = dict (parse_qsl (content ))
95
+
96
+ auth_url_params = {
97
+ 'oauth_token' : request_tokens ['oauth_token' ]
98
+ }
99
+
100
+ accepted_perms = ('read' , 'write' , 'delete' )
101
+ if perms and perms in accepted_perms :
102
+ auth_url_params ['perms' ] = perms
103
+
104
+ request_tokens ['auth_url' ] = '%s?%s' % (self .authorize_url , urllib .urlencode (auth_url_params ))
105
+ return request_tokens
106
+
107
+ def get_auth_tokens (self , oauth_verifier = None ):
108
+ """ Returns 'final' tokens to store and used to make authorized calls to Flickr.
109
+
110
+ Parameters:
111
+ oauth_token - oauth_token returned from when the user is redirected after hitting the get_auth_url() function
112
+ verifier - oauth_verifier returned from when the user is redirected after hitting the get_auth_url() function
113
+ """
114
+
115
+ if not oauth_verifier :
116
+ raise FlickrAuthError ('No OAuth Verifier supplied.' )
117
+
118
+ params = {
119
+ 'oauth_verifier' : oauth_verifier ,
120
+ }
121
+
122
+ resp , content = self .client .request ('%s?%s' % (self .access_token_url , urllib .urlencode (params )), 'GET' )
123
+ if resp ['status' ] != '200' :
124
+ raise FlickrAuthError ('Getting access tokens failed: %s Response Status' % resp ['status' ])
125
+
126
+ return dict (parse_qsl (content ))
127
+
128
+ def api_request (self , endpoint = None , method = 'GET' , params = {}, format = 'json' , files = None ):
129
+ self .headers .update ({'Content-Type' : 'application/json' })
130
+
131
+ if endpoint is None and files is None :
132
+ raise FlickrAPIError ('Please supply an API endpoint to hit.' )
133
+
134
+
135
+ params .update (self .default_params )
136
+ params .update ({'method' : endpoint , 'format' :format })
137
+
138
+ if format == 'json' :
139
+ params ['nojsoncallback' ] = 1
140
+
141
+ if method == 'POST' :
142
+ oauth_params = {
143
+ 'oauth_version' : "1.0" ,
144
+ 'oauth_nonce' : oauth .generate_nonce (),
145
+ 'oauth_timestamp' : int (time .time ())
146
+ }
147
+ params .update (oauth_params )
148
+
149
+ if files is not None :
150
+ files = [('photo' , files , open (files , 'rb' ).read ())]
151
+
152
+ #create a fake request with your upload url and parameters
153
+ faux_req = oauth .Request (method = 'POST' , url = self .upload_api_url , parameters = params )
154
+
155
+ #sign the fake request.
156
+ signature_method = oauth .SignatureMethod_HMAC_SHA1 ()
157
+ faux_req .sign_request (signature_method , self .consumer , self .token )
158
+
159
+ #create a dict out of the fake request signed params
160
+ params = dict (parse_qsl (faux_req .to_postdata ()))
161
+
162
+ content_type , body = self .encode_multipart_formdata (params , files )
163
+ headers = {'Content-Type' : content_type , 'Content-Length' : str (len (body ))}
164
+ r = urllib2 .Request ('%s' % self .upload_api_url , body , headers )
165
+ return urllib2 .urlopen (r ).read ()
166
+
167
+
168
+ req = oauth .Request (method = 'POST' , url = self .rest_api_url , parameters = params )
169
+
170
+ ## Sign the request.
171
+ signature_method = oauth .SignatureMethod_HMAC_SHA1 ()
172
+ req .sign_request (signature_method , self .consumer , self .token )
173
+
174
+ resp , content = self .client .request (req .to_url (), 'POST' , body = req .to_postdata (), headers = self .headers )
175
+ else :
176
+ resp , content = self .client .request ('%s?%s' % (self .rest_api_url , urllib .urlencode (params )), 'GET' , headers = self .headers )
177
+
178
+ status = int (resp ['status' ])
179
+ if status < 200 or status >= 300 :
180
+ raise FlickrAPIError ('Something when wrong making the request, returned a %d code.' % d )
181
+
182
+ #try except for if content is able to be decoded
183
+ try :
184
+ content = json .loads (content )
185
+ except json .JSONDecodeError :
186
+ raise FlickrAPIError ('Content is not valid JSON, unable to be decoded.' )
187
+
188
+ if content .get ('stat' ) and content ['stat' ] == 'fail' :
189
+ raise FlickrAPIError ('Something when wrong finishing the request. Flickr returned Error Code: %d. Message: %s' % (content ['code' ], content ['message' ]))
190
+
191
+ return dict (content )
192
+
193
+ def get (self , endpoint = None , params = {}):
194
+ return self .api_request (endpoint , method = 'GET' , params = params )
195
+
196
+ def post (self , endpoint = None , params = {}, files = None ):
197
+ return self .api_request (endpoint , method = 'POST' , params = params , files = files )
198
+
199
+ @staticmethod
200
+ def encode_multipart_formdata (fields , files ):
201
+ import mimetools
202
+ import mimetypes
203
+ BOUNDARY = mimetools .choose_boundary ()
204
+ CRLF = '\r \n '
205
+ L = []
206
+ for (key , value ) in fields .items ():
207
+ L .append ('--' + BOUNDARY )
208
+ L .append ('Content-Disposition: form-data; name="%s"' % key )
209
+ L .append ('' )
210
+ L .append (value )
211
+ for (key , filename , value ) in files :
212
+ L .append ('--' + BOUNDARY )
213
+ L .append ('Content-Disposition: form-data; name="%s"; filename="%s"' % (key , filename ))
214
+ L .append ('Content-Type: %s' % mimetypes .guess_type (filename )[0 ] or 'application/octet-stream' )
215
+ L .append ('' )
216
+ L .append (value )
217
+ L .append ('--' + BOUNDARY + '--' )
218
+ L .append ('' )
219
+ body = CRLF .join (L )
220
+ content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
221
+ return content_type , body
0 commit comments