From 1a8230bd3e230b9fb04093e4cd6ca8a9f677a2a0 Mon Sep 17 00:00:00 2001 From: "Luis M. Morales S" Date: Sun, 28 Aug 2011 14:39:08 +0200 Subject: [PATCH 1/2] added getUserProfile to retrieve all the information based on user screen_name or id --- .idea/encodings.xml | 5 + .idea/misc.xml | 8 + .idea/modules.xml | 9 + .idea/twython.iml | 9 + .idea/vcs.xml | 7 + twython/twython.py | 773 +++++++++++++++++++++++--------------------- 6 files changed, 443 insertions(+), 368 deletions(-) create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/twython.iml create mode 100644 .idea/vcs.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..ad7b975 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0271f7e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/twython.iml b/.idea/twython.iml new file mode 100644 index 0000000..a34a857 --- /dev/null +++ b/.idea/twython.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..c80f219 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/twython/twython.py b/twython/twython.py index fad12b7..eaa6ea1 100644 --- a/twython/twython.py +++ b/twython/twython.py @@ -34,452 +34,489 @@ from urllib2 import HTTPError # simplejson exists behind the scenes anyway. Past Python 2.6, this should # never really cause any problems to begin with. try: - # Python 2.6 and up - import json as simplejson + # Python 2.6 and up + import json as simplejson except ImportError: - try: - # Python 2.6 and below (2.4/2.5, 2.3 is not guranteed to work with this library to begin with) - import simplejson - except ImportError: - try: - # This case gets rarer by the day, but if we need to, we can pull it from Django provided it's there. - from django.utils import simplejson - except: - # Seriously wtf is wrong with you if you get this Exception. - raise Exception("Twython requires the simplejson library (or Python 2.6) to work. http://www.undefined.org/python/") + try: + # Python 2.6 and below (2.4/2.5, 2.3 is not guranteed to work with this library to begin with) + import simplejson + except ImportError: + try: + # This case gets rarer by the day, but if we need to, we can pull it from Django provided it's there. + from django.utils import simplejson + except: + # Seriously wtf is wrong with you if you get this Exception. + raise Exception( + "Twython requires the simplejson library (or Python 2.6) to work. http://www.undefined.org/python/") # Try and gauge the old OAuth2 library spec. Versions 1.5 and greater no longer have the callback # url as part of the request object; older versions we need to patch for Python 2.5... ugh. ;P OAUTH_CALLBACK_IN_URL = False OAUTH_LIB_SUPPORTS_CALLBACK = False if float(oauth._version.manual_verstr) <= 1.4: - OAUTH_CLIENT_INSPECTION = inspect.getargspec(oauth.Client.request) - try: - OAUTH_LIB_SUPPORTS_CALLBACK = 'callback_url' in OAUTH_CLIENT_INSPECTION.args - except AttributeError: - # Python 2.5 doesn't return named tuples, so don't look for an args section specifically. - OAUTH_LIB_SUPPORTS_CALLBACK = 'callback_url' in OAUTH_CLIENT_INSPECTION + OAUTH_CLIENT_INSPECTION = inspect.getargspec(oauth.Client.request) + try: + OAUTH_LIB_SUPPORTS_CALLBACK = 'callback_url' in OAUTH_CLIENT_INSPECTION.args + except AttributeError: + # Python 2.5 doesn't return named tuples, so don't look for an args section specifically. + OAUTH_LIB_SUPPORTS_CALLBACK = 'callback_url' in OAUTH_CLIENT_INSPECTION else: - OAUTH_CALLBACK_IN_URL = True + OAUTH_CALLBACK_IN_URL = True class TwythonError(AttributeError): - """ - Generic error class, catch-all for most Twython issues. - Special cases are handled by APILimit and AuthError. + """ + Generic error class, catch-all for most Twython issues. + Special cases are handled by APILimit and AuthError. - Note: To use these, the syntax has changed as of Twython 1.3. To catch these, - you need to explicitly import them into your code, e.g: + Note: To use these, the syntax has changed as of Twython 1.3. To catch these, + you need to explicitly import them into your code, e.g: - from twython import TwythonError, APILimit, AuthError - """ - def __init__(self, msg, error_code=None): - self.msg = msg - if error_code == 400: - raise APILimit(msg) + from twython import TwythonError, APILimit, AuthError + """ - def __str__(self): - return repr(self.msg) + def __init__(self, msg, error_code=None): + self.msg = msg + if error_code == 400: + raise APILimit(msg) + + def __str__(self): + return repr(self.msg) class APILimit(TwythonError): - """ - Raised when you've hit an API limit. Try to avoid these, read the API - docs if you're running into issues here, Twython does not concern itself with - this matter beyond telling you that you've done goofed. - """ - def __init__(self, msg): - self.msg = msg + """ + Raised when you've hit an API limit. Try to avoid these, read the API + docs if you're running into issues here, Twython does not concern itself with + this matter beyond telling you that you've done goofed. + """ - def __str__(self): - return repr(self.msg) + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return repr(self.msg) class AuthError(TwythonError): - """ - Raised when you try to access a protected resource and it fails due to some issue with - your authentication. - """ - def __init__(self, msg): - self.msg = msg + """ + Raised when you try to access a protected resource and it fails due to some issue with + your authentication. + """ - def __str__(self): - return repr(self.msg) + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return repr(self.msg) 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={}): - """setup(self, oauth_token = None, headers = None) + def __init__(self, twitter_token=None, twitter_secret=None, oauth_token=None, oauth_token_secret=None, headers=None, + callback_url=None, client_args={}): + """setup(self, oauth_token = None, headers = None) - Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). + Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). - Parameters: - twitter_token - Given to you when you register your application with Twitter. - twitter_secret - Given to you when you register your application with Twitter. - oauth_token - If you've gone through the authentication process and have a token for this user, - pass it in and it'll be used for all requests going forward. - oauth_token_secret - see oauth_token; it's the other half. - headers - User agent header, dictionary style ala {'User-Agent': 'Bert'} - client_args - additional arguments for HTTP client (see httplib2.Http.__init__), e.g. {'timeout': 10.0} + Parameters: + twitter_token - Given to you when you register your application with Twitter. + twitter_secret - Given to you when you register your application with Twitter. + oauth_token - If you've gone through the authentication process and have a token for this user, + pass it in and it'll be used for all requests going forward. + oauth_token_secret - see oauth_token; it's the other half. + headers - User agent header, dictionary style ala {'User-Agent': 'Bert'} + client_args - additional arguments for HTTP client (see httplib2.Http.__init__), e.g. {'timeout': 10.0} - ** Note: versioning is not currently used by search.twitter functions; when Twitter moves their junk, it'll be supported. - """ - # 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 - self.oauth_secret = oauth_token_secret - self.callback_url = callback_url + ** Note: versioning is not currently used by search.twitter functions; when Twitter moves their junk, it'll be supported. + """ + # 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 + self.oauth_secret = oauth_token_secret + self.callback_url = callback_url - # 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'} + # 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'} - consumer = None - token = None + consumer = None + 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) + if self.twitter_token is not None and self.twitter_secret is not None: + 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) + if self.oauth_token is not None and self.oauth_secret is not None: + 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) - else: - # If they don't do authentication, but still want to request unprotected resources, we need an opener. - self.client = httplib2.Http(**client_args) + # 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) + else: + # If they don't do authentication, but still want to request unprotected resources, we need an opener. + self.client = httplib2.Http(**client_args) - def __getattr__(self, api_call): - """ - The most magically awesome block of code you'll see in 2010. + def __getattr__(self, api_call): + """ + The most magically awesome block of code you'll see in 2010. - Rather than list out 9 million damn methods for this API, we just keep a table (see above) of - every API endpoint and their corresponding function id for this library. This pretty much gives - unlimited flexibility in API support - there's a slight chance of a performance hit here, but if this is - going to be your bottleneck... well, don't use Python. ;P + Rather than list out 9 million damn methods for this API, we just keep a table (see above) of + every API endpoint and their corresponding function id for this library. This pretty much gives + unlimited flexibility in API support - there's a slight chance of a performance hit here, but if this is + going to be your bottleneck... well, don't use Python. ;P - For those who don't get what's going on here, Python classes have this great feature known as __getattr__(). - It's called when an attribute that was called on an object doesn't seem to exist - since it doesn't exist, - we can take over and find the API method in our table. We then return a function that downloads and parses - what we're looking for, based on the keywords passed in. + For those who don't get what's going on here, Python classes have this great feature known as __getattr__(). + It's called when an attribute that was called on an object doesn't seem to exist - since it doesn't exist, + we can take over and find the API method in our table. We then return a function that downloads and parses + what we're looking for, based on the keywords passed in. - I'll hate myself for saying this, but this is heavily inspired by Ruby's "method_missing". - """ - def get(self, **kwargs): - # Go through and replace any mustaches that are in our API url. - fn = api_table[api_call] - base = re.sub( - '\{\{(?P[a-zA-Z_]+)\}\}', - lambda m: "%s" % kwargs.get(m.group(1), '1'), # The '1' here catches the API version. Slightly hilarious. - base_url + fn['url'] - ) + I'll hate myself for saying this, but this is heavily inspired by Ruby's "method_missing". + """ - # Then open and load that shiiit, yo. TODO: check HTTP method and junk, handle errors/authentication - if fn['method'] == 'POST': - resp, content = self.client.request(base, fn['method'], urllib.urlencode(dict([k, Twython.encode(v)] for k, v in kwargs.items())), headers = self.headers) - else: - url = base + "?" + "&".join(["%s=%s" %(key, value) for (key, value) in kwargs.iteritems()]) - resp, content = self.client.request(url, fn['method'], headers = self.headers) + def get(self, **kwargs): + # Go through and replace any mustaches that are in our API url. + fn = api_table[api_call] + base = re.sub( + '\{\{(?P[a-zA-Z_]+)\}\}', + lambda m: "%s" % kwargs.get(m.group(1), '1'), + # The '1' here catches the API version. Slightly hilarious. + base_url + fn['url'] + ) - return simplejson.loads(content) + # Then open and load that shiiit, yo. TODO: check HTTP method and junk, handle errors/authentication + if fn['method'] == 'POST': + resp, content = self.client.request(base, fn['method'], urllib.urlencode( + dict([k, Twython.encode(v)] for k, v in kwargs.items())), headers=self.headers) + else: + url = base + "?" + "&".join(["%s=%s" % (key, value) for (key, value) in kwargs.iteritems()]) + resp, content = self.client.request(url, fn['method'], headers=self.headers) - if api_call in api_table: - return get.__get__(self) - else: - raise TwythonError, api_call + return simplejson.loads(content) - def get_authentication_tokens(self): - """ - get_auth_url(self) + if api_call in api_table: + return get.__get__(self) + else: + raise TwythonError, api_call - Returns an authorization URL for a user to hit. - """ - callback_url = self.callback_url or 'oob' - - request_args = {} - if OAUTH_LIB_SUPPORTS_CALLBACK: - request_args['callback_url'] = callback_url - - resp, content = self.client.request(self.request_token_url, "GET", **request_args) + def get_authentication_tokens(self): + """ + get_auth_url(self) - 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)) - - oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed')=='true' - - if not OAUTH_LIB_SUPPORTS_CALLBACK and callback_url != 'oob' and oauth_callback_confirmed: - import warnings - warnings.warn("oauth2 library doesn't support OAuth 1.0a type callback, but remote requires it") - oauth_callback_confirmed = False + Returns an authorization URL for a user to hit. + """ + callback_url = self.callback_url or 'oob' - auth_url_params = { - 'oauth_token' : request_tokens['oauth_token'], - } - - # Use old-style callback argument - if OAUTH_CALLBACK_IN_URL or (callback_url!='oob' and not oauth_callback_confirmed): - auth_url_params['oauth_callback'] = callback_url - - request_tokens['auth_url'] = self.authenticate_url + '?' + urllib.urlencode(auth_url_params) - - return request_tokens + request_args = {} + if OAUTH_LIB_SUPPORTS_CALLBACK: + request_args['callback_url'] = callback_url - def get_authorized_tokens(self): - """ - get_authorized_tokens + resp, content = self.client.request(self.request_token_url, "GET", **request_args) - 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)) - - # ------------------------------------------------------------------------------------------------------------------------ - # The following methods are all different in some manner or require special attention with regards to the Twitter API. - # Because of this, we keep them separate from all the other endpoint definitions - ideally this should be change-able, - # but it's not high on the priority list at the moment. - # ------------------------------------------------------------------------------------------------------------------------ + if resp['status'] != '200': + raise AuthError("Seems something couldn't be verified with your OAuth junk. Error: %s, Message: %s" % ( + resp['status'], content)) - @staticmethod - 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()]) + try: + request_tokens = dict(urlparse.parse_qsl(content)) + except: + request_tokens = dict(cgi.parse_qsl(content)) - @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") + oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed') == 'true' - Shortens url specified by url_to_shorten. + if not OAUTH_LIB_SUPPORTS_CALLBACK and callback_url != 'oob' and oauth_callback_confirmed: + import warnings - 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`) + warnings.warn("oauth2 library doesn't support OAuth 1.0a type callback, but remote requires it") + oauth_callback_confirmed = False - def bulkUserLookup(self, ids = None, screen_names = None, version = 1, **kwargs): - """ bulkUserLookup(self, ids = None, screen_names = None, version = 1, **kwargs) + auth_url_params = { + 'oauth_token': request_tokens['oauth_token'], + } - A method to do bulk user lookups against the Twitter API. Arguments (ids (numbers) / screen_names (strings)) should be flat Arrays that - contain their respective data sets. + # Use old-style callback argument + if OAUTH_CALLBACK_IN_URL or (callback_url != 'oob' and not oauth_callback_confirmed): + auth_url_params['oauth_callback'] = callback_url - Statuses for the users in question will be returned inline if they exist. Requires authentication! - """ - if ids: - kwargs['user_id'] = ','.join(map(str, ids)) - if screen_names: - kwargs['screen_name'] = ','.join(screen_names) - - 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) - except HTTPError, e: - raise TwythonError("bulkUserLookup() failed with a %s error code." % `e.code`, e.code) + request_tokens['auth_url'] = self.authenticate_url + '?' + urllib.urlencode(auth_url_params) - def searchTwitter(self, **kwargs): - """searchTwitter(search_query, **kwargs) + return request_tokens - Returns tweets that match a specified query. + def get_authorized_tokens(self): + """ + get_authorized_tokens - Parameters: - See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. + 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)) - e.g x.searchTwitter(q="jjndf", page="2") - """ - searchURL = Twython.constructApiURL("http://search.twitter.com/search.json", kwargs) - try: - resp, content = self.client.request(searchURL, "GET", headers = self.headers) - return simplejson.loads(content) - except HTTPError, e: - raise TwythonError("getSearchTimeline() failed with a %s error code." % `e.code`, e.code) + # ------------------------------------------------------------------------------------------------------------------------ + # The following methods are all different in some manner or require special attention with regards to the Twitter API. + # Because of this, we keep them separate from all the other endpoint definitions - ideally this should be change-able, + # but it's not high on the priority list at the moment. + # ------------------------------------------------------------------------------------------------------------------------ - def searchTwitterGen(self, search_query, **kwargs): - """searchTwitterGen(search_query, **kwargs) + @staticmethod + 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()]) - Returns a generator of tweets that match a specified query. + @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") - Parameters: - See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. + Shortens url specified by url_to_shorten. - e.g x.searchTwitter(q="jjndf", page="2") - """ - 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) - except HTTPError, e: - raise TwythonError("searchTwitterGen() failed with a %s error code." % `e.code`, e.code) + 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`) - if not data['results']: - raise StopIteration + def bulkUserLookup(self, ids=None, screen_names=None, version=1, **kwargs): + """ bulkUserLookup(self, ids = None, screen_names = None, version = 1, **kwargs) - for tweet in data['results']: - yield tweet + A method to do bulk user lookups against the Twitter API. Arguments (ids (numbers) / screen_names (strings)) should be flat Arrays that + contain their respective data sets. - if 'page' not in kwargs: - kwargs['page'] = 2 - else: - kwargs['page'] += 1 + Statuses for the users in question will be returned inline if they exist. Requires authentication! + """ + if ids: + kwargs['user_id'] = ','.join(map(str, ids)) + if screen_names: + kwargs['screen_name'] = ','.join(screen_names) - for tweet in self.searchTwitterGen(search_query, **kwargs): - yield tweet + 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) + except HTTPError, e: + raise TwythonError("bulkUserLookup() failed with a %s error code." % `e.code`, e.code) - def isListMember(self, list_id, id, username, version = 1): - """ isListMember(self, list_id, id, version) + def searchTwitter(self, **kwargs): + """searchTwitter(search_query, **kwargs) - Check if a specified user (id) is a member of the list in question (list_id). + Returns tweets that match a specified query. - **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. + Parameters: + See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. - Parameters: - list_id - Required. The slug of the list to check against. - id - Required. The ID of the user being checked in the list. - username - User who owns the list you're checking against (username) - 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) - except HTTPError, e: - raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) + e.g x.searchTwitter(q="jjndf", page="2") + """ + searchURL = Twython.constructApiURL("http://search.twitter.com/search.json", kwargs) + try: + resp, content = self.client.request(searchURL, "GET", headers=self.headers) + return simplejson.loads(content) + except HTTPError, e: + raise TwythonError("getSearchTimeline() failed with a %s error code." % `e.code`, e.code) - def isListSubscriber(self, username, list_id, id, version = 1): - """ isListSubscriber(self, list_id, id, version) + def searchTwitterGen(self, search_query, **kwargs): + """searchTwitterGen(search_query, **kwargs) - Check if a specified user (id) is a subscriber of the list in question (list_id). + Returns a generator of tweets that match a specified query. - **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. + Parameters: + See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. - Parameters: - list_id - Required. The slug of the list to check against. - id - Required. The ID of the user being checked in the list. - username - Required. The username of the owner of the list that you're seeing if someone is subscribed to. - 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) - except HTTPError, e: - raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) + e.g x.searchTwitter(q="jjndf", page="2") + """ + 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) + except HTTPError, e: + raise TwythonError("searchTwitterGen() failed with a %s 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") + if not data['results']: + raise StopIteration - Updates the authenticating user's profile background image. + for tweet in data['results']: + yield tweet - 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" - 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) + if 'page' not in kwargs: + kwargs['page'] = 2 + else: + kwargs['page'] += 1 - def updateProfileImage(self, filename, version = 1): - """ updateProfileImage(filename) + for tweet in self.searchTwitterGen(search_query, **kwargs): + yield tweet - Updates the authenticating user's profile image (avatar). + def isListMember(self, list_id, id, username, version=1): + """ isListMember(self, list_id, id, version) - Parameters: - 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) - - def getProfileImageUrl(self, username, size=None, version=1): - """ getProfileImageUrl(username) - - Gets the URL for the user's profile image. - - Parameters: - username - Required. User name of the user you want the image url of. - size - Optional. Image size. Valid options include 'normal', 'mini' and 'bigger'. Defaults to 'normal' if not given. - 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. - """ - url = "http://api.twitter.com/%s/users/profile_image/%s.json" % (version, username) - if size: - url = self.constructApiURL(url, {'size':size}) - - client = httplib2.Http() - client.follow_redirects = False - resp, content = client.request(url, 'GET') - - if resp.status in (301,302,303,307): - return resp['location'] - elif resp.status == 200: - return simplejson.loads(content) - - raise TwythonError("getProfileImageUrl() failed with a %d error code." % resp.status, resp.status) + Check if a specified user (id) is a member of the list in question (list_id). - @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 + **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. - @staticmethod - def unicode2utf8(text): - try: - if isinstance(text, unicode): - text = text.encode('utf-8') - except: - pass - return text + Parameters: + list_id - Required. The slug of the list to check against. + id - Required. The ID of the user being checked in the list. + username - User who owns the list you're checking against (username) + 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) + except HTTPError, e: + raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) - @staticmethod - def encode(text): - if isinstance(text, (str,unicode)): - return Twython.unicode2utf8(text) - return str(text) + def isListSubscriber(self, username, list_id, id, version=1): + """ isListSubscriber(self, list_id, id, version) + + Check if a specified user (id) is a subscriber of the list in question (list_id). + + **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. + + Parameters: + list_id - Required. The slug of the list to check against. + id - Required. The ID of the user being checked in the list. + username - Required. The username of the owner of the list that you're seeing if someone is subscribed to. + 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) + except HTTPError, 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. + 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" + 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) + + def updateProfileImage(self, filename, version=1): + """ updateProfileImage(filename) + + Updates the authenticating user's profile image (avatar). + + Parameters: + 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) + + def getProfileImageUrl(self, username, size=None, version=1): + """ getProfileImageUrl(username) + + Gets the URL for the user's profile image. + + Parameters: + username - Required. User name of the user you want the image url of. + size - Optional. Image size. Valid options include 'normal', 'mini' and 'bigger'. Defaults to 'normal' if not given. + 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. + """ + url = "http://api.twitter.com/%s/users/profile_image/%s.json" % (version, username) + if size: + url = self.constructApiURL(url, {'size': size}) + + client = httplib2.Http() + client.follow_redirects = False + resp, content = client.request(url, 'GET') + + if resp.status in (301, 302, 303, 307): + return resp['location'] + elif resp.status == 200: + return simplejson.loads(content) + + raise TwythonError("getProfileImageUrl() failed with a %d error code." % resp.status, resp.status) + + def getUserProfile(self, **kwargs): + """getUserProfile(screen_name, **kwargs) + + Returns user profile that match a specified screen_name. + + Parameters: + See the documentation at https://dev.twitter.com/docs/api/1/get/users/show. Pass in the API supported arguments as named parameters. + + e.g x.searchTwitter(screen_name="lacion"[, user_id=123[, include_entities=True]]) + """ + userShowURL = Twython.constructApiURL("http://api.twitter.com/version/users/show.json", kwargs) + try: + resp, content = self.client.request(searchURL, "GET", headers=self.headers) + return simplejson.loads(content) + except HTTPError, e: + raise TwythonError("getUserProfile() failed with a %s error code." % `e.code`, e.code) + + @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: + if isinstance(text, unicode): + text = text.encode('utf-8') + except: + pass + return text + + @staticmethod + def encode(text): + if isinstance(text, (str, unicode)): + return Twython.unicode2utf8(text) + return str(text) From b4da4746712e0fff89d7dd5a230afa3ae3837d51 Mon Sep 17 00:00:00 2001 From: Luis Morales Date: Tue, 30 Aug 2011 20:37:13 +0200 Subject: [PATCH 2/2] removed XML files from pycharm, and fixed indentation chaos i created back to original indentation --- .gitignore | 1 + .idea/encodings.xml | 5 - .idea/misc.xml | 8 - .idea/modules.xml | 9 - .idea/twython.iml | 9 - .idea/vcs.xml | 7 - twython/twython.py | 889 ++++++++++++++++++++++---------------------- 7 files changed, 438 insertions(+), 490 deletions(-) delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/twython.iml delete mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index 0b15049..84d2ac9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build dist twython.egg-info *.swp +.idea \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index e206d70..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index ad7b975..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 0271f7e..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/twython.iml b/.idea/twython.iml deleted file mode 100644 index a34a857..0000000 --- a/.idea/twython.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index c80f219..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/twython/twython.py b/twython/twython.py index eaa6ea1..c85dd9d 100644 --- a/twython/twython.py +++ b/twython/twython.py @@ -34,489 +34,474 @@ from urllib2 import HTTPError # simplejson exists behind the scenes anyway. Past Python 2.6, this should # never really cause any problems to begin with. try: - # Python 2.6 and up - import json as simplejson + # Python 2.6 and up + import json as simplejson except ImportError: - try: - # Python 2.6 and below (2.4/2.5, 2.3 is not guranteed to work with this library to begin with) - import simplejson - except ImportError: - try: - # This case gets rarer by the day, but if we need to, we can pull it from Django provided it's there. - from django.utils import simplejson - except: - # Seriously wtf is wrong with you if you get this Exception. - raise Exception( - "Twython requires the simplejson library (or Python 2.6) to work. http://www.undefined.org/python/") + try: + # Python 2.6 and below (2.4/2.5, 2.3 is not guranteed to work with this library to begin with) + import simplejson + except ImportError: + try: + # This case gets rarer by the day, but if we need to, we can pull it from Django provided it's there. + from django.utils import simplejson + except: + # Seriously wtf is wrong with you if you get this Exception. + raise Exception("Twython requires the simplejson library (or Python 2.6) to work. http://www.undefined.org/python/") # Try and gauge the old OAuth2 library spec. Versions 1.5 and greater no longer have the callback # url as part of the request object; older versions we need to patch for Python 2.5... ugh. ;P OAUTH_CALLBACK_IN_URL = False OAUTH_LIB_SUPPORTS_CALLBACK = False if float(oauth._version.manual_verstr) <= 1.4: - OAUTH_CLIENT_INSPECTION = inspect.getargspec(oauth.Client.request) - try: - OAUTH_LIB_SUPPORTS_CALLBACK = 'callback_url' in OAUTH_CLIENT_INSPECTION.args - except AttributeError: - # Python 2.5 doesn't return named tuples, so don't look for an args section specifically. - OAUTH_LIB_SUPPORTS_CALLBACK = 'callback_url' in OAUTH_CLIENT_INSPECTION + OAUTH_CLIENT_INSPECTION = inspect.getargspec(oauth.Client.request) + try: + OAUTH_LIB_SUPPORTS_CALLBACK = 'callback_url' in OAUTH_CLIENT_INSPECTION.args + except AttributeError: + # Python 2.5 doesn't return named tuples, so don't look for an args section specifically. + OAUTH_LIB_SUPPORTS_CALLBACK = 'callback_url' in OAUTH_CLIENT_INSPECTION else: - OAUTH_CALLBACK_IN_URL = True + OAUTH_CALLBACK_IN_URL = True class TwythonError(AttributeError): - """ - Generic error class, catch-all for most Twython issues. - Special cases are handled by APILimit and AuthError. + """ + Generic error class, catch-all for most Twython issues. + Special cases are handled by APILimit and AuthError. - Note: To use these, the syntax has changed as of Twython 1.3. To catch these, - you need to explicitly import them into your code, e.g: + Note: To use these, the syntax has changed as of Twython 1.3. To catch these, + you need to explicitly import them into your code, e.g: - from twython import TwythonError, APILimit, AuthError - """ + from twython import TwythonError, APILimit, AuthError + """ + def __init__(self, msg, error_code=None): + self.msg = msg + if error_code == 400: + raise APILimit(msg) - def __init__(self, msg, error_code=None): - self.msg = msg - if error_code == 400: - raise APILimit(msg) - - def __str__(self): - return repr(self.msg) + def __str__(self): + return repr(self.msg) class APILimit(TwythonError): - """ - Raised when you've hit an API limit. Try to avoid these, read the API - docs if you're running into issues here, Twython does not concern itself with - this matter beyond telling you that you've done goofed. - """ + """ + Raised when you've hit an API limit. Try to avoid these, read the API + docs if you're running into issues here, Twython does not concern itself with + this matter beyond telling you that you've done goofed. + """ + def __init__(self, msg): + self.msg = msg - def __init__(self, msg): - self.msg = msg - - def __str__(self): - return repr(self.msg) + def __str__(self): + return repr(self.msg) class AuthError(TwythonError): - """ - Raised when you try to access a protected resource and it fails due to some issue with - your authentication. - """ + """ + Raised when you try to access a protected resource and it fails due to some issue with + your authentication. + """ + def __init__(self, msg): + self.msg = msg - def __init__(self, msg): - self.msg = msg - - def __str__(self): - return repr(self.msg) + def __str__(self): + return repr(self.msg) 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={}): - """setup(self, oauth_token = None, headers = None) - - Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). - - Parameters: - twitter_token - Given to you when you register your application with Twitter. - twitter_secret - Given to you when you register your application with Twitter. - oauth_token - If you've gone through the authentication process and have a token for this user, - pass it in and it'll be used for all requests going forward. - oauth_token_secret - see oauth_token; it's the other half. - headers - User agent header, dictionary style ala {'User-Agent': 'Bert'} - client_args - additional arguments for HTTP client (see httplib2.Http.__init__), e.g. {'timeout': 10.0} - - ** Note: versioning is not currently used by search.twitter functions; when Twitter moves their junk, it'll be supported. - """ - # 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 - self.oauth_secret = oauth_token_secret - self.callback_url = callback_url - - # 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'} - - consumer = None - 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) - - if self.oauth_token is not None and self.oauth_secret is not None: - 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) - else: - # If they don't do authentication, but still want to request unprotected resources, we need an opener. - self.client = httplib2.Http(**client_args) - - def __getattr__(self, api_call): - """ - The most magically awesome block of code you'll see in 2010. - - Rather than list out 9 million damn methods for this API, we just keep a table (see above) of - every API endpoint and their corresponding function id for this library. This pretty much gives - unlimited flexibility in API support - there's a slight chance of a performance hit here, but if this is - going to be your bottleneck... well, don't use Python. ;P - - For those who don't get what's going on here, Python classes have this great feature known as __getattr__(). - It's called when an attribute that was called on an object doesn't seem to exist - since it doesn't exist, - we can take over and find the API method in our table. We then return a function that downloads and parses - what we're looking for, based on the keywords passed in. - - I'll hate myself for saying this, but this is heavily inspired by Ruby's "method_missing". - """ - - def get(self, **kwargs): - # Go through and replace any mustaches that are in our API url. - fn = api_table[api_call] - base = re.sub( - '\{\{(?P[a-zA-Z_]+)\}\}', - lambda m: "%s" % kwargs.get(m.group(1), '1'), - # The '1' here catches the API version. Slightly hilarious. - base_url + fn['url'] - ) - - # Then open and load that shiiit, yo. TODO: check HTTP method and junk, handle errors/authentication - if fn['method'] == 'POST': - resp, content = self.client.request(base, fn['method'], urllib.urlencode( - dict([k, Twython.encode(v)] for k, v in kwargs.items())), headers=self.headers) - else: - url = base + "?" + "&".join(["%s=%s" % (key, value) for (key, value) in kwargs.iteritems()]) - resp, content = self.client.request(url, fn['method'], headers=self.headers) - - return simplejson.loads(content) - - if api_call in api_table: - return get.__get__(self) - else: - raise TwythonError, api_call - - def get_authentication_tokens(self): - """ - get_auth_url(self) - - Returns an authorization URL for a user to hit. - """ - callback_url = self.callback_url or 'oob' - - request_args = {} - if OAUTH_LIB_SUPPORTS_CALLBACK: - request_args['callback_url'] = callback_url - - resp, content = self.client.request(self.request_token_url, "GET", **request_args) - - 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)) - - oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed') == 'true' - - if not OAUTH_LIB_SUPPORTS_CALLBACK and callback_url != 'oob' and oauth_callback_confirmed: - import warnings - - warnings.warn("oauth2 library doesn't support OAuth 1.0a type callback, but remote requires it") - oauth_callback_confirmed = False - - auth_url_params = { - 'oauth_token': request_tokens['oauth_token'], - } - - # Use old-style callback argument - if OAUTH_CALLBACK_IN_URL or (callback_url != 'oob' and not oauth_callback_confirmed): - auth_url_params['oauth_callback'] = callback_url - - request_tokens['auth_url'] = self.authenticate_url + '?' + urllib.urlencode(auth_url_params) - - return request_tokens - - def get_authorized_tokens(self): - """ - get_authorized_tokens - - 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)) - - # ------------------------------------------------------------------------------------------------------------------------ - # The following methods are all different in some manner or require special attention with regards to the Twitter API. - # Because of this, we keep them separate from all the other endpoint definitions - ideally this should be change-able, - # but it's not high on the priority list at the moment. - # ------------------------------------------------------------------------------------------------------------------------ - - @staticmethod - 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) - - A method to do bulk user lookups against the Twitter API. Arguments (ids (numbers) / screen_names (strings)) should be flat Arrays that - contain their respective data sets. - - Statuses for the users in question will be returned inline if they exist. Requires authentication! - """ - if ids: - kwargs['user_id'] = ','.join(map(str, ids)) - if screen_names: - kwargs['screen_name'] = ','.join(screen_names) - - 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) - except HTTPError, e: - raise TwythonError("bulkUserLookup() failed with a %s error code." % `e.code`, e.code) - - def searchTwitter(self, **kwargs): - """searchTwitter(search_query, **kwargs) - - Returns tweets that match a specified query. - - Parameters: - See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. - - e.g x.searchTwitter(q="jjndf", page="2") - """ - searchURL = Twython.constructApiURL("http://search.twitter.com/search.json", kwargs) - try: - resp, content = self.client.request(searchURL, "GET", headers=self.headers) - return simplejson.loads(content) - except HTTPError, e: - raise TwythonError("getSearchTimeline() failed with a %s error code." % `e.code`, e.code) - - def searchTwitterGen(self, search_query, **kwargs): - """searchTwitterGen(search_query, **kwargs) - - Returns a generator of tweets that match a specified query. - - Parameters: - See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. - - e.g x.searchTwitter(q="jjndf", page="2") - """ - 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) - except HTTPError, e: - raise TwythonError("searchTwitterGen() failed with a %s error code." % `e.code`, e.code) - - if not data['results']: - raise StopIteration - - for tweet in data['results']: - yield tweet - - if 'page' not in kwargs: - kwargs['page'] = 2 - else: - kwargs['page'] += 1 - - for tweet in self.searchTwitterGen(search_query, **kwargs): - yield tweet - - def isListMember(self, list_id, id, username, version=1): - """ isListMember(self, list_id, id, version) - - Check if a specified user (id) is a member of the list in question (list_id). - - **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. - - Parameters: - list_id - Required. The slug of the list to check against. - id - Required. The ID of the user being checked in the list. - username - User who owns the list you're checking against (username) - 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) - except HTTPError, e: - raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) - - def isListSubscriber(self, username, list_id, id, version=1): - """ isListSubscriber(self, list_id, id, version) - - Check if a specified user (id) is a subscriber of the list in question (list_id). - - **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. - - Parameters: - list_id - Required. The slug of the list to check against. - id - Required. The ID of the user being checked in the list. - username - Required. The username of the owner of the list that you're seeing if someone is subscribed to. - 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) - except HTTPError, 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. - 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" - 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) - - def updateProfileImage(self, filename, version=1): - """ updateProfileImage(filename) - - Updates the authenticating user's profile image (avatar). - - Parameters: - 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) - - def getProfileImageUrl(self, username, size=None, version=1): - """ getProfileImageUrl(username) - - Gets the URL for the user's profile image. - - Parameters: - username - Required. User name of the user you want the image url of. - size - Optional. Image size. Valid options include 'normal', 'mini' and 'bigger'. Defaults to 'normal' if not given. - 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. - """ - url = "http://api.twitter.com/%s/users/profile_image/%s.json" % (version, username) - if size: - url = self.constructApiURL(url, {'size': size}) - - client = httplib2.Http() - client.follow_redirects = False - resp, content = client.request(url, 'GET') - - if resp.status in (301, 302, 303, 307): - return resp['location'] - elif resp.status == 200: - return simplejson.loads(content) - - raise TwythonError("getProfileImageUrl() failed with a %d error code." % resp.status, resp.status) - - def getUserProfile(self, **kwargs): - """getUserProfile(screen_name, **kwargs) + def __init__(self, twitter_token = None, twitter_secret = None, oauth_token = None, oauth_token_secret = None, headers=None, callback_url=None, client_args={}): + """setup(self, oauth_token = None, headers = None) + + Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). + + Parameters: + twitter_token - Given to you when you register your application with Twitter. + twitter_secret - Given to you when you register your application with Twitter. + oauth_token - If you've gone through the authentication process and have a token for this user, + pass it in and it'll be used for all requests going forward. + oauth_token_secret - see oauth_token; it's the other half. + headers - User agent header, dictionary style ala {'User-Agent': 'Bert'} + client_args - additional arguments for HTTP client (see httplib2.Http.__init__), e.g. {'timeout': 10.0} + + ** Note: versioning is not currently used by search.twitter functions; when Twitter moves their junk, it'll be supported. + """ + # 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 + self.oauth_secret = oauth_token_secret + self.callback_url = callback_url + + # 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'} + + consumer = None + 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) + + if self.oauth_token is not None and self.oauth_secret is not None: + 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) + else: + # If they don't do authentication, but still want to request unprotected resources, we need an opener. + self.client = httplib2.Http(**client_args) + + def __getattr__(self, api_call): + """ + The most magically awesome block of code you'll see in 2010. + + Rather than list out 9 million damn methods for this API, we just keep a table (see above) of + every API endpoint and their corresponding function id for this library. This pretty much gives + unlimited flexibility in API support - there's a slight chance of a performance hit here, but if this is + going to be your bottleneck... well, don't use Python. ;P + + For those who don't get what's going on here, Python classes have this great feature known as __getattr__(). + It's called when an attribute that was called on an object doesn't seem to exist - since it doesn't exist, + we can take over and find the API method in our table. We then return a function that downloads and parses + what we're looking for, based on the keywords passed in. + + I'll hate myself for saying this, but this is heavily inspired by Ruby's "method_missing". + """ + def get(self, **kwargs): + # Go through and replace any mustaches that are in our API url. + fn = api_table[api_call] + base = re.sub( + '\{\{(?P[a-zA-Z_]+)\}\}', + lambda m: "%s" % kwargs.get(m.group(1), '1'), # The '1' here catches the API version. Slightly hilarious. + base_url + fn['url'] + ) + + # Then open and load that shiiit, yo. TODO: check HTTP method and junk, handle errors/authentication + if fn['method'] == 'POST': + resp, content = self.client.request(base, fn['method'], urllib.urlencode(dict([k, Twython.encode(v)] for k, v in kwargs.items())), headers = self.headers) + else: + url = base + "?" + "&".join(["%s=%s" %(key, value) for (key, value) in kwargs.iteritems()]) + resp, content = self.client.request(url, fn['method'], headers = self.headers) + + return simplejson.loads(content) + + if api_call in api_table: + return get.__get__(self) + else: + raise TwythonError, api_call + + def get_authentication_tokens(self): + """ + get_auth_url(self) + + Returns an authorization URL for a user to hit. + """ + callback_url = self.callback_url or 'oob' + + request_args = {} + if OAUTH_LIB_SUPPORTS_CALLBACK: + request_args['callback_url'] = callback_url + + resp, content = self.client.request(self.request_token_url, "GET", **request_args) + + 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)) + + oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed')=='true' + + if not OAUTH_LIB_SUPPORTS_CALLBACK and callback_url != 'oob' and oauth_callback_confirmed: + import warnings + warnings.warn("oauth2 library doesn't support OAuth 1.0a type callback, but remote requires it") + oauth_callback_confirmed = False + + auth_url_params = { + 'oauth_token' : request_tokens['oauth_token'], + } + + # Use old-style callback argument + if OAUTH_CALLBACK_IN_URL or (callback_url!='oob' and not oauth_callback_confirmed): + auth_url_params['oauth_callback'] = callback_url + + request_tokens['auth_url'] = self.authenticate_url + '?' + urllib.urlencode(auth_url_params) + + return request_tokens + + def get_authorized_tokens(self): + """ + get_authorized_tokens + + 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)) + + # ------------------------------------------------------------------------------------------------------------------------ + # The following methods are all different in some manner or require special attention with regards to the Twitter API. + # Because of this, we keep them separate from all the other endpoint definitions - ideally this should be change-able, + # but it's not high on the priority list at the moment. + # ------------------------------------------------------------------------------------------------------------------------ + + @staticmethod + 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) + + A method to do bulk user lookups against the Twitter API. Arguments (ids (numbers) / screen_names (strings)) should be flat Arrays that + contain their respective data sets. + + Statuses for the users in question will be returned inline if they exist. Requires authentication! + """ + if ids: + kwargs['user_id'] = ','.join(map(str, ids)) + if screen_names: + kwargs['screen_name'] = ','.join(screen_names) + + 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) + except HTTPError, e: + raise TwythonError("bulkUserLookup() failed with a %s error code." % `e.code`, e.code) + + def searchTwitter(self, **kwargs): + """searchTwitter(search_query, **kwargs) + + Returns tweets that match a specified query. + + Parameters: + See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. + + e.g x.searchTwitter(q="jjndf", page="2") + """ + searchURL = Twython.constructApiURL("http://search.twitter.com/search.json", kwargs) + try: + resp, content = self.client.request(searchURL, "GET", headers = self.headers) + return simplejson.loads(content) + except HTTPError, e: + raise TwythonError("getSearchTimeline() failed with a %s error code." % `e.code`, e.code) + + def searchTwitterGen(self, search_query, **kwargs): + """searchTwitterGen(search_query, **kwargs) + + Returns a generator of tweets that match a specified query. + + Parameters: + See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. + + e.g x.searchTwitter(q="jjndf", page="2") + """ + 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) + except HTTPError, e: + raise TwythonError("searchTwitterGen() failed with a %s error code." % `e.code`, e.code) + + if not data['results']: + raise StopIteration + + for tweet in data['results']: + yield tweet + + if 'page' not in kwargs: + kwargs['page'] = 2 + else: + kwargs['page'] += 1 + + for tweet in self.searchTwitterGen(search_query, **kwargs): + yield tweet + + def isListMember(self, list_id, id, username, version = 1): + """ isListMember(self, list_id, id, version) + + Check if a specified user (id) is a member of the list in question (list_id). + + **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. + + Parameters: + list_id - Required. The slug of the list to check against. + id - Required. The ID of the user being checked in the list. + username - User who owns the list you're checking against (username) + 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) + except HTTPError, e: + raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) + + def isListSubscriber(self, username, list_id, id, version = 1): + """ isListSubscriber(self, list_id, id, version) + + Check if a specified user (id) is a subscriber of the list in question (list_id). + + **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. + + Parameters: + list_id - Required. The slug of the list to check against. + id - Required. The ID of the user being checked in the list. + username - Required. The username of the owner of the list that you're seeing if someone is subscribed to. + 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) + except HTTPError, 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. + 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" + 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) + + def updateProfileImage(self, filename, version = 1): + """ updateProfileImage(filename) + + Updates the authenticating user's profile image (avatar). + + Parameters: + 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) + + def getProfileImageUrl(self, username, size=None, version=1): + """ getProfileImageUrl(username) + + Gets the URL for the user's profile image. + + Parameters: + username - Required. User name of the user you want the image url of. + size - Optional. Image size. Valid options include 'normal', 'mini' and 'bigger'. Defaults to 'normal' if not given. + 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. + """ + url = "http://api.twitter.com/%s/users/profile_image/%s.json" % (version, username) + if size: + url = self.constructApiURL(url, {'size':size}) + + client = httplib2.Http() + client.follow_redirects = False + resp, content = client.request(url, 'GET') + + if resp.status in (301,302,303,307): + return resp['location'] + elif resp.status == 200: + return simplejson.loads(content) + + raise TwythonError("getProfileImageUrl() failed with a %d error code." % resp.status, resp.status) + + def getUserProfile(self, **kwargs): + """getUserProfile(**kwargs) + + Parameters: + username - Required. User name of the user you want the profile information. + OR + id - Required. User id of the user you want the profile information. Returns user profile that match a specified screen_name. Parameters: See the documentation at https://dev.twitter.com/docs/api/1/get/users/show. Pass in the API supported arguments as named parameters. - e.g x.searchTwitter(screen_name="lacion"[, user_id=123[, include_entities=True]]) + e.g x.getUserProfile(screen_name="lacion"[, user_id=123[, include_entities=True]]) """ - userShowURL = Twython.constructApiURL("http://api.twitter.com/version/users/show.json", kwargs) - try: - resp, content = self.client.request(searchURL, "GET", headers=self.headers) - return simplejson.loads(content) - except HTTPError, e: - raise TwythonError("getUserProfile() failed with a %s error code." % `e.code`, e.code) + userShowURL = Twython.constructApiURL("http://api.twitter.com/1/users/show.json", kwargs) + try: + resp, content = self.client.request(userShowURL, "GET", headers=self.headers) + return simplejson.loads(content) + except HTTPError, e: + raise TwythonError("getUserProfile() failed with a %s error code." % `e.code`, e.code) - @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 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: - if isinstance(text, unicode): - text = text.encode('utf-8') - except: - pass - return text + @staticmethod + def unicode2utf8(text): + try: + if isinstance(text, unicode): + text = text.encode('utf-8') + except: + pass + return text - @staticmethod - def encode(text): - if isinstance(text, (str, unicode)): - return Twython.unicode2utf8(text) - return str(text) + @staticmethod + def encode(text): + if isinstance(text, (str,unicode)): + return Twython.unicode2utf8(text) + return str(text) \ No newline at end of file