Skip to content

Commit f1aa0e8

Browse files
Initial commit.
0 parents  commit f1aa0e8

File tree

5 files changed

+352
-0
lines changed

5 files changed

+352
-0
lines changed

.gitignore

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
*.mo
2+
*.egg-info
3+
*.egg
4+
*.EGG
5+
*.EGG-INFO
6+
bin
7+
build
8+
develop-eggs
9+
downloads
10+
eggs
11+
fake-eggs
12+
parts
13+
dist
14+
.installed.cfg
15+
.mr.developer.cfg
16+
.hg
17+
.bzr
18+
.svn
19+
*.pyc
20+
*.pyo
21+
*.tmp*

MANIFEST.in

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include README.md

README.md

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#Overview
2+
I've been writing up APIs interfacing with OAuth in Python like no other the past week or so, so here is another... Flickr API.
3+
4+
Hope this documentation explains everything you need to get started. Any questions feel free to email me or inbox me.
5+
6+
#Authorization URL
7+
*Get an authorization url for your user*
8+
9+
```python
10+
f = FlickrAPI(api_key='*your app key*',
11+
api_secret='*your app secret*',
12+
callback_url='http://www.example.com/callback/')
13+
14+
auth_props = f.get_authentication_tokens()
15+
auth_url = auth_props['auth_url']
16+
17+
#Store this token in a session or something for later use in the next step.
18+
oauth_token_secret = auth_props['oauth_token_secret']
19+
20+
print 'Connect with Flickr via: %s' % auth_url
21+
```
22+
23+
Once you click "Allow" be sure that there is a URL set up to handle getting finalized tokens and possibly adding them to your database to use their information at a later date. \n\n'
24+
25+
#Handling the callback
26+
```python
27+
# In Django, you'd do something like
28+
# oauth_token = request.GET.get('oauth_verifier')
29+
# oauth_verifier = request.GET.get('oauth_verifier')
30+
31+
oauth_token = *Grab oauth token from URL*
32+
oauth_verifier = *Grab oauth verifier from URL*
33+
34+
#Initiate the FlickrAPI class in your callback.
35+
f = FlickrAPI(api_key='*your app key*',
36+
api_secret='*your app secret*',
37+
oauth_token=oauth_token,
38+
oauth_token_secret=session['flickr_session_keys']['oauth_token_secret'])
39+
40+
authorized_tokens = f.get_auth_tokens(oauth_verifier)
41+
42+
final_oauth_token = authorized_tokens['oauth_token']
43+
final_oauth_token_secret = authorized_tokens['oauth_token_secret']
44+
45+
# Save those tokens to the database for a later use?
46+
```
47+
48+
#Getting some user information & adding comments to photos.
49+
```python
50+
# Get the final tokens from the database or wherever you have them stored
51+
52+
f = FlickrAPI(api_key = '*your app key*',
53+
api_secret = '*your app secret*',
54+
oauth_token=final_tokens['oauth_token'],
55+
oauth_token_secret=final_tokens['oauth_token_secret'])
56+
57+
# Return the users recent activity feed.
58+
recent_activity = f.get('flickr.activity.userComments')
59+
print recent_activity
60+
61+
# Add comment on a photo
62+
add_comment = f.post('flickr.photos.comments.addComment', params={'photo_id':'6620847285', 'comment_text':'This is a test comment.'})
63+
64+
#This returns the comment id if successful.
65+
print add_comment
66+
67+
# Remove comment on a photo
68+
# If the comment is already deleted, it will throw a FlickrAPIError (In this case, with code 2: Comment not found.)
69+
del_comment = f.post('flickr.photos.comments.deleteComment', params={'comment_id':'45887890-6620847285-72157628767110559'})
70+
print del_comment
71+
```
72+
73+
#Uploading a Photo
74+
```python
75+
f = FlickrAPI(api_key = '*your app key*',
76+
api_secret = '*your app secret*',
77+
oauth_token=final_tokens['oauth_token'],
78+
oauth_token_secret=final_tokens['oauth_token_secret'])
79+
80+
f.post(params={'title':'Test Title!'}, files='/path/to/image.jpg')
81+
```

flickr.py

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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

setup.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env python
2+
3+
import sys, os
4+
from setuptools import setup
5+
import flickr
6+
7+
setup(
8+
name='python-flickr',
9+
version=flickr.__version__,
10+
install_requires=['httplib2', 'oauth2', 'simplejson'],
11+
author='Mike Helmick',
12+
author_email='[email protected]',
13+
license='MIT License',
14+
url='https://github.com/michaelhelmick/python-flickr/',
15+
keywords='python flickr oauth api',
16+
description='A Python Library to interface with Flickr REST API & OAuth',
17+
long_description=open('README.md').read(),
18+
download_url="https://github.com/michaelhelmick/python-flickr/zipball/master",
19+
py_modules=["flickr"],
20+
classifiers = [
21+
'Development Status :: 4 - Beta',
22+
'Intended Audience :: Developers',
23+
'License :: OSI Approved :: MIT License',
24+
'Topic :: Software Development :: Libraries :: Python Modules',
25+
'Topic :: Communications :: Chat',
26+
'Topic :: Internet'
27+
]
28+
)

0 commit comments

Comments
 (0)