From e54183df9cb2654a228fb30edcacd17de8949e85 Mon Sep 17 00:00:00 2001 From: Michael Helmick Date: Thu, 12 Jan 2012 22:37:50 -0500 Subject: [PATCH] Uploading profile image and profile background image, update setup required packages, removed some funcs. * You can now update user profile image or user profile background image thanks to the Python requests library. * Updated setup to include 'requests' as a required package * Changed to beginning hashbang to use the users environment python version * try/except for parse_qsl, removed try/excepts where it used cgi.parse_qsl/urlparse.parse_sql * Lines 161/162 (using self.consumer/token) <- this addition ended up not being needed, but it doesn't hurt. * updateProfileBackgroundImage() - param 'tile' is now True/False rather than a string "true" or string "false" * removed encode_multipart_formdata func, not needed any longer --- setup.py | 2 +- twython/twython.py | 126 ++++++++++++++++++++++----------------------- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/setup.py b/setup.py index 90f94a1..a75f04b 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( include_package_data = True, # Package dependencies. - install_requires = ['simplejson', 'oauth2', 'httplib2'], + install_requires = ['simplejson', 'oauth2', 'httplib2', 'requests'], # Metadata for PyPI. author = 'Ryan McGrath', diff --git a/twython/twython.py b/twython/twython.py index d8db763..abcf0de 100644 --- a/twython/twython.py +++ b/twython/twython.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env """ Twython is a library for Python that wraps the Twitter API. @@ -21,9 +21,16 @@ import mimetypes import mimetools import re import inspect +import time +import requests import oauth2 as oauth +try: + from urlparse import parse_qsl +except ImportError: + from cgi import parse_qsl + # Twython maps keyword based arguments to Twitter API endpoints. The endpoints # table is a file with a dictionary of every API endpoint that Twython supports. from twitter_endpoints import base_url, api_table @@ -151,20 +158,20 @@ class Twython(object): if self.headers is None: self.headers = {'User-agent': 'Twython Python Twitter Library v1.3'} - consumer = None - token = None + self.consumer = None + self.token = None if self.twitter_token is not None and self.twitter_secret is not None: - consumer = oauth.Consumer(self.twitter_token, self.twitter_secret) + self.consumer = oauth.Consumer(self.twitter_token, self.twitter_secret) if self.oauth_token is not None and self.oauth_secret is not None: - token = oauth.Token(oauth_token, oauth_token_secret) + self.token = oauth.Token(oauth_token, oauth_token_secret) # Filter down through the possibilities here - if they have a token, if they're first stage, etc. - if consumer is not None and token is not None: - self.client = oauth.Client(consumer, token, **client_args) - elif consumer is not None: - self.client = oauth.Client(consumer, **client_args) + if self.consumer is not None and self.token is not None: + self.client = oauth.Client(self.consumer, self.token, **client_args) + elif self.consumer is not None: + self.client = oauth.Client(self.consumer, **client_args) else: # If they don't do authentication, but still want to request unprotected resources, we need an opener. self.client = httplib2.Http(**client_args) @@ -225,10 +232,7 @@ class Twython(object): if resp['status'] != '200': raise AuthError("Seems something couldn't be verified with your OAuth junk. Error: %s, Message: %s" % (resp['status'], content)) - try: - request_tokens = dict(urlparse.parse_qsl(content)) - except: - request_tokens = dict(cgi.parse_qsl(content)) + request_tokens = dict(parse_qsl(content)) oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed')=='true' @@ -256,10 +260,7 @@ class Twython(object): Returns authorized tokens after they go through the auth_url phase. """ resp, content = self.client.request(self.access_token_url, "GET") - try: - return dict(urlparse.parse_qsl(content)) - except: - return dict(cgi.parse_qsl(content)) + return dict(parse_qsl(content)) # ------------------------------------------------------------------------------------------------------------------------ # The following methods are all different in some manner or require special attention with regards to the Twitter API. @@ -421,26 +422,36 @@ class Twython(object): raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) # The following methods are apart from the other Account methods, because they rely on a whole multipart-data posting function set. - def updateProfileBackgroundImage(self, filename, tile="true", version = 1): - """ updateProfileBackgroundImage(filename, tile="true") + def updateProfileBackgroundImage(self, filename, tile=True, version = 1): + """ updateProfileBackgroundImage(filename, tile=True) Updates the authenticating user's profile background image. Parameters: image - Required. Must be a valid GIF, JPG, or PNG image of less than 800 kilobytes in size. Images with width larger than 2048 pixels will be forceably scaled down. - tile - Optional (defaults to true). If set to true the background image will be displayed tiled. The image will not be tiled otherwise. - ** Note: It's sad, but when using this method, pass the tile value as a string, e.g tile="false" + tile - Optional (defaults to True). If set to true the background image will be displayed tiled. The image will not be tiled otherwise. version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. """ - try: - files = [("image", filename, open(filename, 'rb').read())] - fields = [] - content_type, body = Twython.encode_multipart_formdata(fields, files) - headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} - r = urllib2.Request("http://api.twitter.com/%d/account/update_profile_background_image.json?tile=%s" % (version, tile), body, headers) - return urllib2.urlopen(r).read() - except HTTPError, e: - raise TwythonError("updateProfileBackgroundImage() failed with a %d error code." % e.code, e.code) + upload_url = 'http://api.twitter.com/%d/account/update_profile_background_image.json' % version + params = { + 'oauth_version': "1.0", + 'oauth_nonce': oauth.generate_nonce(length=41), + 'oauth_timestamp': int(time.time()), + } + + #create a fake request with your upload url and parameters + faux_req = oauth.Request(method='POST', url=upload_url, parameters=params) + + #sign the fake request. + signature_method = oauth.SignatureMethod_HMAC_SHA1() + faux_req.sign_request(signature_method, self.consumer, self.token) + + #create a dict out of the fake request signed params + params = dict(parse_qsl(faux_req.to_postdata())) + self.headers.update(faux_req.to_header()) + + req = requests.post(upload_url, data={'tile':tile}, files={'image':(filename, open(filename, 'rb'))}, headers=self.headers) + return req.content def updateProfileImage(self, filename, version = 1): """ updateProfileImage(filename) @@ -451,15 +462,26 @@ class Twython(object): image - Required. Must be a valid GIF, JPG, or PNG image of less than 700 kilobytes in size. Images with width larger than 500 pixels will be scaled down. version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. """ - try: - files = [("image", filename, open(filename, 'rb').read())] - fields = [] - content_type, body = Twython.encode_multipart_formdata(fields, files) - headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} - r = urllib2.Request("http://api.twitter.com/%d/account/update_profile_image.json" % version, body, headers) - return urllib2.urlopen(r).read() - except HTTPError, e: - raise TwythonError("updateProfileImage() failed with a %d error code." % e.code, e.code) + upload_url = 'http://api.twitter.com/%d/account/update_profile_image.json' % version + params = { + 'oauth_version': "1.0", + 'oauth_nonce': oauth.generate_nonce(length=41), + 'oauth_timestamp': int(time.time()), + } + + #create a fake request with your upload url and parameters + faux_req = oauth.Request(method='POST', url=upload_url, parameters=params) + + #sign the fake request. + signature_method = oauth.SignatureMethod_HMAC_SHA1() + faux_req.sign_request(signature_method, self.consumer, self.token) + + #create a dict out of the fake request signed params + params = dict(parse_qsl(faux_req.to_postdata())) + self.headers.update(faux_req.to_header()) + + req = requests.post(upload_url, files={'image':(filename, open(filename, 'rb'))}, headers=self.headers) + return req.content def getProfileImageUrl(self, username, size=None, version=1): """ getProfileImageUrl(username) @@ -485,29 +507,7 @@ class Twython(object): return simplejson.loads(content.decode('utf-8')) raise TwythonError("getProfileImageUrl() failed with a %d error code." % resp.status, resp.status) - - @staticmethod - def encode_multipart_formdata(fields, files): - BOUNDARY = mimetools.choose_boundary() - CRLF = '\r\n' - L = [] - for (key, value) in fields: - L.append('--' + BOUNDARY) - L.append('Content-Disposition: form-data; name="%s"' % key) - L.append('') - L.append(value) - for (key, filename, value) in files: - L.append('--' + BOUNDARY) - L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) - L.append('Content-Type: %s' % mimetypes.guess_type(filename)[0] or 'application/octet-stream') - L.append('') - L.append(value) - L.append('--' + BOUNDARY + '--') - L.append('') - body = CRLF.join(L) - content_type = 'multipart/form-data; boundary=%s' % BOUNDARY - return content_type, body - + @staticmethod def unicode2utf8(text): try: @@ -521,4 +521,4 @@ class Twython(object): def encode(text): if isinstance(text, (str,unicode)): return Twython.unicode2utf8(text) - return str(text) + return str(text) \ No newline at end of file