From e3d9ed656b008b147656a074c08f06d4d3ae0334 Mon Sep 17 00:00:00 2001 From: Michael Helmick Date: Tue, 6 Mar 2012 16:58:27 -0500 Subject: [PATCH 1/3] PEP8 Cleanup on Twitter Endpoints --- twython/twitter_endpoints.py | 636 +++++++++++++++++------------------ 1 file changed, 318 insertions(+), 318 deletions(-) diff --git a/twython/twitter_endpoints.py b/twython/twitter_endpoints.py index 030d110..cf4690b 100644 --- a/twython/twitter_endpoints.py +++ b/twython/twitter_endpoints.py @@ -1,330 +1,330 @@ """ - A huge map of every Twitter API endpoint to a function definition in Twython. + A huge map of every Twitter API endpoint to a function definition in Twython. - Parameters that need to be embedded in the URL are treated with mustaches, e.g: + Parameters that need to be embedded in the URL are treated with mustaches, e.g: - {{version}}, etc + {{version}}, etc - When creating new endpoint definitions, keep in mind that the name of the mustache - will be replaced with the keyword that gets passed in to the function at call time. + When creating new endpoint definitions, keep in mind that the name of the mustache + will be replaced with the keyword that gets passed in to the function at call time. - i.e, in this case, if I pass version = 47 to any function, {{version}} will be replaced - with 47, instead of defaulting to 1 (said defaulting takes place at conversion time). + i.e, in this case, if I pass version = 47 to any function, {{version}} will be replaced + with 47, instead of defaulting to 1 (said defaulting takes place at conversion time). """ # Base Twitter API url, no need to repeat this junk... base_url = 'http://api.twitter.com/{{version}}' -api_table = { - 'getRateLimitStatus': { - 'url': '/account/rate_limit_status.json', - 'method': 'GET', - }, - - 'verifyCredentials': { - 'url': '/account/verify_credentials.json', - 'method': 'GET', - }, - - 'endSession' : { - 'url': '/account/end_session.json', - 'method': 'POST', - }, - - # Timeline methods - 'getPublicTimeline': { - 'url': '/statuses/public_timeline.json', - 'method': 'GET', - }, - 'getHomeTimeline': { - 'url': '/statuses/home_timeline.json', - 'method': 'GET', - }, - 'getUserTimeline': { - 'url': '/statuses/user_timeline.json', - 'method': 'GET', - }, - 'getFriendsTimeline': { - 'url': '/statuses/friends_timeline.json', - 'method': 'GET', - }, - - # Interfacing with friends/followers - 'getUserMentions': { - 'url': '/statuses/mentions.json', - 'method': 'GET', - }, - 'getFriendsStatus': { - 'url': '/statuses/friends.json', - 'method': 'GET', - }, - 'getFollowersStatus': { - 'url': '/statuses/followers.json', - 'method': 'GET', - }, - 'createFriendship': { - 'url': '/friendships/create.json', - 'method': 'POST', - }, - 'destroyFriendship': { - 'url': '/friendships/destroy.json', - 'method': 'POST', - }, - 'getFriendsIDs': { - 'url': '/friends/ids.json', - 'method': 'GET', - }, - 'getFollowersIDs': { - 'url': '/followers/ids.json', - 'method': 'GET', - }, - 'getIncomingFriendshipIDs': { - 'url': '/friendships/incoming.json', - 'method': 'GET', - }, - 'getOutgoingFriendshipIDs': { - 'url': '/friendships/outgoing.json', - 'method': 'GET', - }, - - # Retweets - 'reTweet': { - 'url': '/statuses/retweet/{{id}}.json', - 'method': 'POST', - }, - 'getRetweets': { - 'url': '/statuses/retweets/{{id}}.json', - 'method': 'GET', - }, - 'retweetedOfMe': { - 'url': '/statuses/retweets_of_me.json', - 'method': 'GET', - }, - 'retweetedByMe': { - 'url': '/statuses/retweeted_by_me.json', - 'method': 'GET', - }, - 'retweetedToMe': { - 'url': '/statuses/retweeted_to_me.json', - 'method': 'GET', - }, - - # User methods - 'showUser': { - 'url': '/users/show.json', - 'method': 'GET', - }, - 'searchUsers': { - 'url': '/users/search.json', - 'method': 'GET', - }, - - 'lookupUser': { - 'url': '/users/lookup.json', - 'method': 'GET', - }, - - # Status methods - showing, updating, destroying, etc. - 'showStatus': { - 'url': '/statuses/show/{{id}}.json', - 'method': 'GET', - }, - 'updateStatus': { - 'url': '/statuses/update.json', - 'method': 'POST', - }, - 'destroyStatus': { - 'url': '/statuses/destroy/{{id}}.json', - 'method': 'POST', - }, - - # Direct Messages - getting, sending, effing, etc. - 'getDirectMessages': { - 'url': '/direct_messages.json', - 'method': 'GET', - }, - 'getSentMessages': { - 'url': '/direct_messages/sent.json', - 'method': 'GET', - }, - 'sendDirectMessage': { - 'url': '/direct_messages/new.json', - 'method': 'POST', - }, - 'destroyDirectMessage': { - 'url': '/direct_messages/destroy/{{id}}.json', - 'method': 'POST', - }, - - # Friendship methods - 'checkIfFriendshipExists': { - 'url': '/friendships/exists.json', - 'method': 'GET', - }, - 'showFriendship': { - 'url': '/friendships/show.json', - 'method': 'GET', - }, - - # Profile methods - 'updateProfile': { - 'url': '/account/update_profile.json', - 'method': 'POST', - }, - 'updateProfileColors': { - 'url': '/account/update_profile_colors.json', - 'method': 'POST', - }, - - # Favorites methods - 'getFavorites': { - 'url': '/favorites.json', - 'method': 'GET', - }, - 'createFavorite': { - 'url': '/favorites/create/{{id}}.json', - 'method': 'POST', - }, - 'destroyFavorite': { - 'url': '/favorites/destroy/{{id}}.json', - 'method': 'POST', - }, - - # Blocking methods - 'createBlock': { - 'url': '/blocks/create/{{id}}.json', - 'method': 'POST', - }, - 'destroyBlock': { - 'url': '/blocks/destroy/{{id}}.json', - 'method': 'POST', - }, - 'getBlocking': { - 'url': '/blocks/blocking.json', - 'method': 'GET', - }, - 'getBlockedIDs': { - 'url': '/blocks/blocking/ids.json', - 'method': 'GET', - }, - 'checkIfBlockExists': { - 'url': '/blocks/exists.json', - 'method': 'GET', - }, - - # Trending methods - 'getCurrentTrends': { - 'url': '/trends/current.json', - 'method': 'GET', - }, - 'getDailyTrends': { - 'url': '/trends/daily.json', - 'method': 'GET', - }, - 'getWeeklyTrends': { - 'url': '/trends/weekly.json', - 'method': 'GET', - }, - 'availableTrends': { - 'url': '/trends/available.json', - 'method': 'GET', - }, - 'trendsByLocation': { - 'url': '/trends/{{woeid}}.json', - 'method': 'GET', - }, - - # Saved Searches - 'getSavedSearches': { - 'url': '/saved_searches.json', - 'method': 'GET', - }, - 'showSavedSearch': { - 'url': '/saved_searches/show/{{id}}.json', - 'method': 'GET', - }, - 'createSavedSearch': { - 'url': '/saved_searches/create.json', - 'method': 'GET', - }, - 'destroySavedSearch': { - 'url': '/saved_searches/destroy/{{id}}.json', - 'method': 'GET', - }, - - # List API methods/endpoints. Fairly exhaustive and annoying in general. ;P - 'createList': { - 'url': '/{{username}}/lists.json', - 'method': 'POST', - }, - 'updateList': { - 'url': '/{{username}}/lists/{{list_id}}.json', - 'method': 'POST', - }, - 'showLists': { - 'url': '/{{username}}/lists.json', - 'method': 'GET', - }, - 'getListMemberships': { - 'url': '/{{username}}/lists/memberships.json', - 'method': 'GET', - }, - 'getListSubscriptions': { - 'url': '/{{username}}/lists/subscriptions.json', - 'method': 'GET', - }, - 'deleteList': { - 'url': '/{{username}}/lists/{{list_id}}.json', - 'method': 'DELETE', - }, - 'getListTimeline': { - 'url': '/{{username}}/lists/{{list_id}}/statuses.json', - 'method': 'GET', - }, - 'getSpecificList': { - 'url': '/{{username}}/lists/{{list_id}}/statuses.json', - 'method': 'GET', - }, - 'addListMember': { - 'url': '/{{username}}/{{list_id}}/members.json', - 'method': 'POST', - }, - 'getListMembers': { - 'url': '/{{username}}/{{list_id}}/members.json', - 'method': 'GET', - }, - 'deleteListMember': { - 'url': '/{{username}}/{{list_id}}/members.json', - 'method': 'DELETE', - }, - 'getListSubscribers': { - 'url': '/{{username}}/{{list_id}}/subscribers.json', - 'method': 'GET', - }, - 'subscribeToList': { - 'url': '/{{username}}/{{list_id}}/subscribers.json', - 'method': 'POST', - }, - 'unsubscribeFromList': { - 'url': '/{{username}}/{{list_id}}/subscribers.json', - 'method': 'DELETE', - }, +api_table = { + 'getRateLimitStatus': { + 'url': '/account/rate_limit_status.json', + 'method': 'GET', + }, - # The one-offs - 'notificationFollow': { - 'url': '/notifications/follow/follow.json', - 'method': 'POST', - }, - 'notificationLeave': { - 'url': '/notifications/leave/leave.json', - 'method': 'POST', - }, - 'updateDeliveryService': { - 'url': '/account/update_delivery_device.json', - 'method': 'POST', - }, - 'reportSpam': { - 'url': '/report_spam.json', - 'method': 'POST', - }, + 'verifyCredentials': { + 'url': '/account/verify_credentials.json', + 'method': 'GET', + }, + + 'endSession': { + 'url': '/account/end_session.json', + 'method': 'POST', + }, + + # Timeline methods + 'getPublicTimeline': { + 'url': '/statuses/public_timeline.json', + 'method': 'GET', + }, + 'getHomeTimeline': { + 'url': '/statuses/home_timeline.json', + 'method': 'GET', + }, + 'getUserTimeline': { + 'url': '/statuses/user_timeline.json', + 'method': 'GET', + }, + 'getFriendsTimeline': { + 'url': '/statuses/friends_timeline.json', + 'method': 'GET', + }, + + # Interfacing with friends/followers + 'getUserMentions': { + 'url': '/statuses/mentions.json', + 'method': 'GET', + }, + 'getFriendsStatus': { + 'url': '/statuses/friends.json', + 'method': 'GET', + }, + 'getFollowersStatus': { + 'url': '/statuses/followers.json', + 'method': 'GET', + }, + 'createFriendship': { + 'url': '/friendships/create.json', + 'method': 'POST', + }, + 'destroyFriendship': { + 'url': '/friendships/destroy.json', + 'method': 'POST', + }, + 'getFriendsIDs': { + 'url': '/friends/ids.json', + 'method': 'GET', + }, + 'getFollowersIDs': { + 'url': '/followers/ids.json', + 'method': 'GET', + }, + 'getIncomingFriendshipIDs': { + 'url': '/friendships/incoming.json', + 'method': 'GET', + }, + 'getOutgoingFriendshipIDs': { + 'url': '/friendships/outgoing.json', + 'method': 'GET', + }, + + # Retweets + 'reTweet': { + 'url': '/statuses/retweet/{{id}}.json', + 'method': 'POST', + }, + 'getRetweets': { + 'url': '/statuses/retweets/{{id}}.json', + 'method': 'GET', + }, + 'retweetedOfMe': { + 'url': '/statuses/retweets_of_me.json', + 'method': 'GET', + }, + 'retweetedByMe': { + 'url': '/statuses/retweeted_by_me.json', + 'method': 'GET', + }, + 'retweetedToMe': { + 'url': '/statuses/retweeted_to_me.json', + 'method': 'GET', + }, + + # User methods + 'showUser': { + 'url': '/users/show.json', + 'method': 'GET', + }, + 'searchUsers': { + 'url': '/users/search.json', + 'method': 'GET', + }, + + 'lookupUser': { + 'url': '/users/lookup.json', + 'method': 'GET', + }, + + # Status methods - showing, updating, destroying, etc. + 'showStatus': { + 'url': '/statuses/show/{{id}}.json', + 'method': 'GET', + }, + 'updateStatus': { + 'url': '/statuses/update.json', + 'method': 'POST', + }, + 'destroyStatus': { + 'url': '/statuses/destroy/{{id}}.json', + 'method': 'POST', + }, + + # Direct Messages - getting, sending, effing, etc. + 'getDirectMessages': { + 'url': '/direct_messages.json', + 'method': 'GET', + }, + 'getSentMessages': { + 'url': '/direct_messages/sent.json', + 'method': 'GET', + }, + 'sendDirectMessage': { + 'url': '/direct_messages/new.json', + 'method': 'POST', + }, + 'destroyDirectMessage': { + 'url': '/direct_messages/destroy/{{id}}.json', + 'method': 'POST', + }, + + # Friendship methods + 'checkIfFriendshipExists': { + 'url': '/friendships/exists.json', + 'method': 'GET', + }, + 'showFriendship': { + 'url': '/friendships/show.json', + 'method': 'GET', + }, + + # Profile methods + 'updateProfile': { + 'url': '/account/update_profile.json', + 'method': 'POST', + }, + 'updateProfileColors': { + 'url': '/account/update_profile_colors.json', + 'method': 'POST', + }, + + # Favorites methods + 'getFavorites': { + 'url': '/favorites.json', + 'method': 'GET', + }, + 'createFavorite': { + 'url': '/favorites/create/{{id}}.json', + 'method': 'POST', + }, + 'destroyFavorite': { + 'url': '/favorites/destroy/{{id}}.json', + 'method': 'POST', + }, + + # Blocking methods + 'createBlock': { + 'url': '/blocks/create/{{id}}.json', + 'method': 'POST', + }, + 'destroyBlock': { + 'url': '/blocks/destroy/{{id}}.json', + 'method': 'POST', + }, + 'getBlocking': { + 'url': '/blocks/blocking.json', + 'method': 'GET', + }, + 'getBlockedIDs': { + 'url': '/blocks/blocking/ids.json', + 'method': 'GET', + }, + 'checkIfBlockExists': { + 'url': '/blocks/exists.json', + 'method': 'GET', + }, + + # Trending methods + 'getCurrentTrends': { + 'url': '/trends/current.json', + 'method': 'GET', + }, + 'getDailyTrends': { + 'url': '/trends/daily.json', + 'method': 'GET', + }, + 'getWeeklyTrends': { + 'url': '/trends/weekly.json', + 'method': 'GET', + }, + 'availableTrends': { + 'url': '/trends/available.json', + 'method': 'GET', + }, + 'trendsByLocation': { + 'url': '/trends/{{woeid}}.json', + 'method': 'GET', + }, + + # Saved Searches + 'getSavedSearches': { + 'url': '/saved_searches.json', + 'method': 'GET', + }, + 'showSavedSearch': { + 'url': '/saved_searches/show/{{id}}.json', + 'method': 'GET', + }, + 'createSavedSearch': { + 'url': '/saved_searches/create.json', + 'method': 'GET', + }, + 'destroySavedSearch': { + 'url': '/saved_searches/destroy/{{id}}.json', + 'method': 'GET', + }, + + # List API methods/endpoints. Fairly exhaustive and annoying in general. ;P + 'createList': { + 'url': '/{{username}}/lists.json', + 'method': 'POST', + }, + 'updateList': { + 'url': '/{{username}}/lists/{{list_id}}.json', + 'method': 'POST', + }, + 'showLists': { + 'url': '/{{username}}/lists.json', + 'method': 'GET', + }, + 'getListMemberships': { + 'url': '/{{username}}/lists/memberships.json', + 'method': 'GET', + }, + 'getListSubscriptions': { + 'url': '/{{username}}/lists/subscriptions.json', + 'method': 'GET', + }, + 'deleteList': { + 'url': '/{{username}}/lists/{{list_id}}.json', + 'method': 'DELETE', + }, + 'getListTimeline': { + 'url': '/{{username}}/lists/{{list_id}}/statuses.json', + 'method': 'GET', + }, + 'getSpecificList': { + 'url': '/{{username}}/lists/{{list_id}}/statuses.json', + 'method': 'GET', + }, + 'addListMember': { + 'url': '/{{username}}/{{list_id}}/members.json', + 'method': 'POST', + }, + 'getListMembers': { + 'url': '/{{username}}/{{list_id}}/members.json', + 'method': 'GET', + }, + 'deleteListMember': { + 'url': '/{{username}}/{{list_id}}/members.json', + 'method': 'DELETE', + }, + 'getListSubscribers': { + 'url': '/{{username}}/{{list_id}}/subscribers.json', + 'method': 'GET', + }, + 'subscribeToList': { + 'url': '/{{username}}/{{list_id}}/subscribers.json', + 'method': 'POST', + }, + 'unsubscribeFromList': { + 'url': '/{{username}}/{{list_id}}/subscribers.json', + 'method': 'DELETE', + }, + + # The one-offs + 'notificationFollow': { + 'url': '/notifications/follow/follow.json', + 'method': 'POST', + }, + 'notificationLeave': { + 'url': '/notifications/leave/leave.json', + 'method': 'POST', + }, + 'updateDeliveryService': { + 'url': '/account/update_delivery_device.json', + 'method': 'POST', + }, + 'reportSpam': { + 'url': '/report_spam.json', + 'method': 'POST', + }, } -- 2.39.5 From 8630dc3f03af5390eed1e0598f25f7d0e50146d0 Mon Sep 17 00:00:00 2001 From: Michael Helmick Date: Thu, 8 Mar 2012 12:20:04 -0500 Subject: [PATCH 2/3] Twython using requests/requests-oauth --- twython/twython.py | 177 +++++++++++++++++++++++++++++---------------- 1 file changed, 115 insertions(+), 62 deletions(-) diff --git a/twython/twython.py b/twython/twython.py index 6d38ac3..a2a125e 100644 --- a/twython/twython.py +++ b/twython/twython.py @@ -13,12 +13,13 @@ __version__ = "1.4.6" import urllib import urllib2 -import httplib2 import re import inspect import time import requests +from requests.exceptions import RequestException +from oauth_hook import OAuthHook import oauth2 as oauth try: @@ -124,7 +125,7 @@ class TwythonAuthError(TwythonError): class Twython(object): def __init__(self, twitter_token=None, twitter_secret=None, oauth_token=None, oauth_token_secret=None, \ - headers=None, callback_url=None, client_args=None): + headers=None, callback_url=None): """setup(self, oauth_token = None, headers = None) Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). @@ -141,11 +142,16 @@ class Twython(object): ** Note: versioning is not currently used by search.twitter functions; when Twitter moves their junk, it'll be supported. """ + + OAuthHook.consumer_key = twitter_token + OAuthHook.consumer_secret = twitter_secret + # Needed for hitting that there API. self.request_token_url = 'http://twitter.com/oauth/request_token' self.access_token_url = 'http://twitter.com/oauth/access_token' self.authorize_url = 'http://twitter.com/oauth/authorize' self.authenticate_url = 'http://twitter.com/oauth/authenticate' + self.twitter_token = twitter_token self.twitter_secret = twitter_secret self.oauth_token = oauth_token @@ -155,29 +161,23 @@ class Twython(object): # If there's headers, set them, otherwise be an embarassing parent for their own good. self.headers = headers if self.headers is None: - self.headers = {'User-agent': 'Twython Python Twitter Library v1.3'} + self.headers = {'User-agent': 'Twython Python Twitter Library v1.4.6'} - self.consumer = None - self.token = None - - client_args = client_args or {} + self.client = None if self.twitter_token is not None and self.twitter_secret is not None: - self.consumer = oauth.Consumer(self.twitter_token, self.twitter_secret) + self.client = requests.session(hooks={'pre_request': OAuthHook()}) if self.oauth_token is not None and self.oauth_secret is not None: - self.token = oauth.Token(oauth_token, oauth_token_secret) + self.oauth_hook = OAuthHook(self.oauth_token, self.oauth_secret) + self.client = requests.session(hooks={'pre_request': self.oauth_hook}) # Filter down through the possibilities here - if they have a token, if they're first stage, etc. - 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 self.client is None: # If they don't do authentication, but still want to request unprotected resources, we need an opener. - self.client = httplib2.Http(**client_args) - # register available funcs to allow listing name when debugging. + self.client = requests.session() + # register available funcs to allow listing name when debugging. def setFunc(key): return lambda **kwargs: self._constructFunc(key, **kwargs) for key in api_table.keys(): @@ -194,17 +194,19 @@ class Twython(object): base_url + fn['url'] ) - # Then open and load that shiiit, yo. TODO: check HTTP method - # and junk, handle errors/authentication - if fn['method'] == 'POST': - myargs = urllib.urlencode(dict([k, Twython.encode(v)] for k, v in kwargs.items())) - resp, content = self.client.request(base, fn['method'], myargs, headers=self.headers) - else: - myargs = ["%s=%s" % (key, value) for (key, value) in kwargs.iteritems()] - url = "%s?%s" % (base, "&".join(myargs)) - resp, content = self.client.request(url, fn['method'], headers=self.headers) + method = fn['method'].lower() + if not method in ('get', 'post', 'delete'): + raise TwythonError('Method must be of GET, POST or DELETE') - return simplejson.loads(content.decode('utf-8')) + if method == 'get': + myargs = ['%s=%s' % (key, value) for (key, value) in kwargs.iteritems()] + else: + myargs = kwargs + + func = getattr(self.client, method) + response = func(base, data=myargs) + + return simplejson.loads(response.content.decode('utf-8')) def get_authentication_tokens(self): """ @@ -218,12 +220,14 @@ class Twython(object): if OAUTH_LIB_SUPPORTS_CALLBACK: request_args['callback_url'] = callback_url - resp, content = self.client.request(self.request_token_url, "GET", **request_args) + response = self.client.get(self.request_token_url, **request_args) - if resp['status'] != '200': - raise TwythonAuthError("Seems something couldn't be verified with your OAuth junk. Error: %s, Message: %s" % (resp['status'], content)) + if response.status_code != 200: + raise TwythonAuthError("Seems something couldn't be verified with your OAuth junk. Error: %s, Message: %s" % (response.status_code, response.content)) - request_tokens = dict(parse_qsl(content)) + request_tokens = dict(parse_qsl(response.content)) + if not request_tokens: + raise TwythonError('Unable to decode request tokens.') oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed') == 'true' @@ -250,8 +254,13 @@ class Twython(object): Returns authorized tokens after they go through the auth_url phase. """ - resp, content = self.client.request(self.access_token_url, "GET") - return dict(parse_qsl(content)) + + response = self.client.get(self.access_token_url) + authorized_tokens = dict(parse_qsl(response.content)) + if not authorized_tokens: + raise TwythonError('Unable to decode authorized tokens.') + + return authorized_tokens # ------------------------------------------------------------------------------------------------------------------------ # The following methods are all different in some manner or require special attention with regards to the Twitter API. @@ -294,9 +303,9 @@ class Twython(object): lookupURL = Twython.constructApiURL("http://api.twitter.com/%d/users/lookup.json" % version, kwargs) try: - resp, content = self.client.request(lookupURL, "POST", headers=self.headers) - return simplejson.loads(content.decode('utf-8')) - except HTTPError, e: + response = self.client.post(lookupURL, headers=self.headers) + return simplejson.loads(response.content.decode('utf-8')) + except RequestException, e: raise TwythonError("bulkUserLookup() failed with a %s error code." % e.code, e.code) def search(self, **kwargs): @@ -311,17 +320,17 @@ class Twython(object): """ searchURL = Twython.constructApiURL("http://search.twitter.com/search.json", kwargs) try: - resp, content = self.client.request(searchURL, "GET", headers=self.headers) + response = self.client.get(searchURL, headers=self.headers) - if int(resp.status) == 420: - retry_wait_seconds = resp['retry-after'] + if response.status_code == 420: + retry_wait_seconds = response.headers.get('retry-after') raise TwythonRateLimitError("getSearchTimeline() is being rate limited. Retry after %s seconds." % retry_wait_seconds, retry_wait_seconds, - resp.status) + response.status_code) - return simplejson.loads(content.decode('utf-8')) - except HTTPError, e: + return simplejson.loads(response.content.decode('utf-8')) + except RequestException, e: raise TwythonError("getSearchTimeline() failed with a %s error code." % e.code, e.code) def searchTwitter(self, **kwargs): @@ -342,9 +351,9 @@ class Twython(object): """ searchURL = Twython.constructApiURL("http://search.twitter.com/search.json?q=%s" % Twython.unicode2utf8(search_query), kwargs) try: - resp, content = self.client.request(searchURL, "GET", headers=self.headers) - data = simplejson.loads(content.decode('utf-8')) - except HTTPError, e: + response = self.client.get(searchURL, headers=self.headers) + data = simplejson.loads(response.content.decode('utf-8')) + except RequestException, e: raise TwythonError("searchGen() failed with a %s error code." % e.code, e.code) if not data['results']: @@ -388,9 +397,9 @@ class Twython(object): 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: - resp, content = self.client.request("http://api.twitter.com/%d/%s/%s/members/%s.json" % (version, username, list_id, id), headers=self.headers) - return simplejson.loads(content.decode('utf-8')) - except HTTPError, e: + response = self.client.get("http://api.twitter.com/%d/%s/%s/members/%s.json" % (version, username, list_id, id), headers=self.headers) + return simplejson.loads(response.content.decode('utf-8')) + except RequestException, e: raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) def isListSubscriber(self, username, list_id, id, version=1): @@ -407,9 +416,9 @@ class Twython(object): 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: - resp, content = self.client.request("http://api.twitter.com/%d/%s/%s/following/%s.json" % (version, username, list_id, id), headers=self.headers) - return simplejson.loads(content.decode('utf-8')) - except HTTPError, e: + response = self.client.get("http://api.twitter.com/%d/%s/%s/following/%s.json" % (version, username, list_id, id), headers=self.headers) + return simplejson.loads(response.content.decode('utf-8')) + except RequestException, e: 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. @@ -448,10 +457,35 @@ class Twython(object): """ return self._media_update('https://upload.twitter.com/%d/statuses/update_with_media.json' % version, {'media': (file_, open(file_, 'rb'))}, **params) - def _media_update(self, url, file_, params={}): + def _media_update(self, url, file_, params=None): + params = params or {} + + ''' + *** + Techincally, this code will work one day. :P + I think @kennethreitz is working with somebody to + get actual OAuth stuff implemented into `requests` + Until then we will have to use `request-oauth` and + currently the code below should work, but doesn't. + + See this gist (https://gist.github.com/2002119) + request-oauth is missing oauth_body_hash from the + header.. that MIGHT be why it's not working.. + I haven't debugged enough. + + - Mike Helmick + *** + + self.oauth_hook.header_auth = True + self.client = requests.session(hooks={'pre_request': self.oauth_hook}) + print self.oauth_hook + response = self.client.post(url, data=params, files=file_, headers=self.headers) + print response.headers + return response.content + ''' oauth_params = { - 'oauth_version': "1.0", - 'oauth_nonce': oauth.generate_nonce(length=41), + 'oauth_consumer_key': self.oauth_hook.consumer_key, + 'oauth_token': self.oauth_token, 'oauth_timestamp': int(time.time()), } @@ -460,7 +494,28 @@ class Twython(object): #sign the fake request. signature_method = oauth.SignatureMethod_HMAC_SHA1() - faux_req.sign_request(signature_method, self.consumer, self.token) + + class dotdict(dict): + """ + This is a helper func. because python-oauth2 wants a + dict in dot notation. + """ + + def __getattr__(self, attr): + return self.get(attr, None) + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ + + consumer = { + 'key': self.oauth_hook.consumer_key, + 'secret': self.oauth_hook.consumer_secret + } + token = { + 'key': self.oauth_token, + 'secret': self.oauth_secret + } + + faux_req.sign_request(signature_method, dotdict(consumer), dotdict(token)) #create a dict out of the fake request signed params self.headers.update(faux_req.to_header()) @@ -482,16 +537,14 @@ class Twython(object): if size: url = self.constructApiURL(url, {'size': size}) - client = httplib2.Http() - client.follow_redirects = False - resp, content = client.request(url, 'GET') + #client.follow_redirects = False + response = self.client.get(url, allow_redirects=False) + image_url = response.headers.get('location') - if resp.status in (301, 302, 303, 307): - return resp['location'] - elif resp.status == 200: - return simplejson.loads(content.decode('utf-8')) + if response.status_code in (301, 302, 303, 307) and image_url is not None: + return image_url - raise TwythonError("getProfileImageUrl() failed with a %d error code." % resp.status, resp.status) + raise TwythonError("getProfileImageUrl() failed with a %d error code." % response.status_code, response.status_code) @staticmethod def unicode2utf8(text): -- 2.39.5 From 158bf77231f8ad09221205db53aa18ce21df95e3 Mon Sep 17 00:00:00 2001 From: Michael Helmick Date: Thu, 8 Mar 2012 12:24:03 -0500 Subject: [PATCH 3/3] Remove httplib2 dependency, remove "shortenUrl" function, no need for urllib2 either * Removed shortenUrl since Twitter ALWAYS shortens the URL to a t.co, anyways. * Since removing shortenUrl, no need for urllib2 anymore * No need for httplib2 anymore, either --- setup.py | 49 +++++++++++++++++++++++----------------------- twython/twython.py | 18 ----------------- 2 files changed, 24 insertions(+), 43 deletions(-) diff --git a/setup.py b/setup.py index aa9eb71..aa4d164 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -import sys, os from setuptools import setup from setuptools import find_packages @@ -8,31 +7,31 @@ __author__ = 'Ryan McGrath ' __version__ = '1.4.6' setup( - # Basic package information. - name = 'twython', - version = __version__, - packages = find_packages(), + # Basic package information. + name='twython', + version=__version__, + packages=find_packages(), - # Packaging options. - include_package_data = True, + # Packaging options. + include_package_data=True, - # Package dependencies. - install_requires = ['simplejson', 'oauth2', 'httplib2', 'requests'], + # Package dependencies. + install_requires=['simplejson', 'oauth2', 'requests', 'requests-oauth'], - # Metadata for PyPI. - author = 'Ryan McGrath', - author_email = 'ryan@venodesigns.net', - license = 'MIT License', - url = 'http://github.com/ryanmcgrath/twython/tree/master', - keywords = 'twitter search api tweet twython', - description = 'An easy (and up to date) way to access Twitter data with Python.', - long_description = open('README.markdown').read(), - classifiers = [ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Communications :: Chat', - 'Topic :: Internet' - ] + # Metadata for PyPI. + author='Ryan McGrath', + author_email='ryan@venodesigns.net', + license='MIT License', + url='http://github.com/ryanmcgrath/twython/tree/master', + keywords='twitter search api tweet twython', + description='An easy (and up to date) way to access Twitter data with Python.', + long_description=open('README.markdown').read(), + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Communications :: Chat', + 'Topic :: Internet' + ] ) diff --git a/twython/twython.py b/twython/twython.py index a2a125e..80ed8ea 100644 --- a/twython/twython.py +++ b/twython/twython.py @@ -12,7 +12,6 @@ __author__ = "Ryan McGrath " __version__ = "1.4.6" import urllib -import urllib2 import re import inspect import time @@ -31,7 +30,6 @@ except ImportError: # table is a file with a dictionary of every API endpoint that Twython supports. from twitter_endpoints import base_url, api_table -from urllib2 import HTTPError # There are some special setups (like, oh, a Django application) where # simplejson exists behind the scenes anyway. Past Python 2.6, this should @@ -272,22 +270,6 @@ class Twython(object): def constructApiURL(base_url, params): return base_url + "?" + "&".join(["%s=%s" % (Twython.unicode2utf8(key), urllib.quote_plus(Twython.unicode2utf8(value))) for (key, value) in params.iteritems()]) - @staticmethod - def shortenURL(url_to_shorten, shortener="http://is.gd/api.php", query="longurl"): - """shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl") - - Shortens url specified by url_to_shorten. - - Parameters: - url_to_shorten - URL to shorten. - shortener - In case you want to use a url shortening service other than is.gd. - """ - try: - content = urllib2.urlopen(shortener + "?" + urllib.urlencode({query: Twython.unicode2utf8(url_to_shorten)})).read() - return content - except HTTPError, e: - raise TwythonError("shortenURL() failed with a %s error code." % e.code) - def bulkUserLookup(self, ids=None, screen_names=None, version=1, **kwargs): """ bulkUserLookup(self, ids = None, screen_names = None, version = 1, **kwargs) -- 2.39.5