Merge 813626a9ad into 4042407970
This commit is contained in:
commit
4f35d571d2
3 changed files with 116 additions and 57 deletions
2
setup.py
2
setup.py
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
||||||
from setuptools import find_packages
|
from setuptools import find_packages
|
||||||
|
|
||||||
__author__ = 'Ryan McGrath <ryan@venodesigns.net>'
|
__author__ = 'Ryan McGrath <ryan@venodesigns.net>'
|
||||||
__version__ = '1.5.2'
|
__version__ = '1.6.0'
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
# Basic package information.
|
# Basic package information.
|
||||||
|
|
|
||||||
|
|
@ -330,16 +330,16 @@ api_table = {
|
||||||
}
|
}
|
||||||
|
|
||||||
# from https://dev.twitter.com/docs/error-codes-responses
|
# from https://dev.twitter.com/docs/error-codes-responses
|
||||||
twitter_http_status_codes= {
|
twitter_http_status_codes = {
|
||||||
200 : ('OK','Success!'),
|
200: ('OK', 'Success!'),
|
||||||
304 : ('Not Modified','There was no new data to return.'),
|
304: ('Not Modified', 'There was no new data to return.'),
|
||||||
400 : ('Bad Request','The request was invalid. An accompanying error message will explain why. This is the status code will be returned during rate limiting.'),
|
400: ('Bad Request', 'The request was invalid. An accompanying error message will explain why. This is the status code will be returned during rate limiting.'),
|
||||||
401 : ('Unauthorized','Authentication credentials were missing or incorrect.'),
|
401: ('Unauthorized', 'Authentication credentials were missing or incorrect.'),
|
||||||
403 : ('Forbidden','The request is understood, but it has been refused. An accompanying error message will explain why. This code is used when requests are being denied due to update limits.'),
|
403: ('Forbidden', 'The request is understood, but it has been refused. An accompanying error message will explain why. This code is used when requests are being denied due to update limits.'),
|
||||||
404 : ('Not Found','The URI requested is invalid or the resource requested, such as a user, does not exists.'),
|
404: ('Not Found', 'The URI requested is invalid or the resource requested, such as a user, does not exists.'),
|
||||||
406 : ('Not Acceptable','Returned by the Search API when an invalid format is specified in the request.'),
|
406: ('Not Acceptable', 'Returned by the Search API when an invalid format is specified in the request.'),
|
||||||
420 : ('Enhance Your Calm','Returned by the Search and Trends API when you are being rate limited.'),
|
420: ('Enhance Your Calm', 'Returned by the Search and Trends API when you are being rate limited.'),
|
||||||
500 : ('Internal Server Error','Something is broken. Please post to the group so the Twitter team can investigate.'),
|
500: ('Internal Server Error', 'Something is broken. Please post to the group so the Twitter team can investigate.'),
|
||||||
502 : ('Bad Gateway','Twitter is down or being upgraded.'),
|
502: ('Bad Gateway', 'Twitter is down or being upgraded.'),
|
||||||
503 : ('Service Unavailable','The Twitter servers are up, but overloaded with requests. Try again later.'),
|
503: ('Service Unavailable', 'The Twitter servers are up, but overloaded with requests. Try again later.'),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__author__ = "Ryan McGrath <ryan@venodesigns.net>"
|
__author__ = "Ryan McGrath <ryan@venodesigns.net>"
|
||||||
__version__ = "1.5.2"
|
__version__ = "1.6.0"
|
||||||
|
|
||||||
import urllib
|
import urllib
|
||||||
import re
|
import re
|
||||||
|
|
@ -27,19 +27,21 @@ except ImportError:
|
||||||
|
|
||||||
# Twython maps keyword based arguments to Twitter API endpoints. The endpoints
|
# Twython maps keyword based arguments to Twitter API endpoints. The endpoints
|
||||||
# table is a file with a dictionary of every API endpoint that Twython supports.
|
# table is a file with a dictionary of every API endpoint that Twython supports.
|
||||||
from twitter_endpoints import base_url, api_table , twitter_http_status_codes
|
from twitter_endpoints import base_url, api_table, twitter_http_status_codes
|
||||||
|
|
||||||
|
|
||||||
# There are some special setups (like, oh, a Django application) where
|
# There are some special setups (like, oh, a Django application) where
|
||||||
# simplejson exists behind the scenes anyway. Past Python 2.6, this should
|
# simplejson exists behind the scenes anyway. Past Python 2.6, this should
|
||||||
# never really cause any problems to begin with.
|
# never really cause any problems to begin with.
|
||||||
try:
|
try:
|
||||||
# Python 2.6 and up
|
# Python 2.6 and below (2.4/2.5, 2.3 is not guranteed to work with this library to begin with)
|
||||||
import json as simplejson
|
# If they have simplejson, we should try and load that first,
|
||||||
|
# if they have the library, chances are they're gonna want to use that.
|
||||||
|
import simplejson
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
try:
|
||||||
# Python 2.6 and below (2.4/2.5, 2.3 is not guranteed to work with this library to begin with)
|
# Python 2.6 and up
|
||||||
import simplejson
|
import json as simplejson
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
try:
|
||||||
# This case gets rarer by the day, but if we need to, we can pull it from Django provided it's there.
|
# This case gets rarer by the day, but if we need to, we can pull it from Django provided it's there.
|
||||||
|
|
@ -48,6 +50,7 @@ except ImportError:
|
||||||
# Seriously wtf is wrong with you if you get this Exception.
|
# 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/")
|
raise Exception("Twython requires the simplejson library (or Python 2.6) to work. http://www.undefined.org/python/")
|
||||||
|
|
||||||
|
|
||||||
class TwythonError(AttributeError):
|
class TwythonError(AttributeError):
|
||||||
"""
|
"""
|
||||||
Generic error class, catch-all for most Twython issues.
|
Generic error class, catch-all for most Twython issues.
|
||||||
|
|
@ -60,8 +63,15 @@ class TwythonError(AttributeError):
|
||||||
"""
|
"""
|
||||||
def __init__(self, msg, error_code=None):
|
def __init__(self, msg, error_code=None):
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
if error_code == 400:
|
|
||||||
raise TwythonAPILimit(msg)
|
if error_code is not None and error_code in twitter_http_status_codes:
|
||||||
|
self.msg = '%s: %s -- %s' % \
|
||||||
|
(twitter_http_status_codes[error_code][0],
|
||||||
|
twitter_http_status_codes[error_code][1],
|
||||||
|
self.msg)
|
||||||
|
|
||||||
|
if error_code == 400 or error_code == 420:
|
||||||
|
raise TwythonAPILimit(self.msg)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return repr(self.msg)
|
return repr(self.msg)
|
||||||
|
|
@ -159,6 +169,7 @@ class Twython(object):
|
||||||
self.access_token_url = 'http://twitter.com/oauth/access_token'
|
self.access_token_url = 'http://twitter.com/oauth/access_token'
|
||||||
self.authorize_url = 'http://twitter.com/oauth/authorize'
|
self.authorize_url = 'http://twitter.com/oauth/authorize'
|
||||||
self.authenticate_url = 'http://twitter.com/oauth/authenticate'
|
self.authenticate_url = 'http://twitter.com/oauth/authenticate'
|
||||||
|
self.api_url = 'http://api.twitter.com/%s/'
|
||||||
|
|
||||||
self.twitter_token = twitter_token
|
self.twitter_token = twitter_token
|
||||||
self.twitter_secret = twitter_secret
|
self.twitter_secret = twitter_secret
|
||||||
|
|
@ -177,7 +188,7 @@ class Twython(object):
|
||||||
self.client = requests.session(hooks={'pre_request': OAuthHook()})
|
self.client = requests.session(hooks={'pre_request': OAuthHook()})
|
||||||
|
|
||||||
if self.oauth_token is not None and self.oauth_secret is not None:
|
if self.oauth_token is not None and self.oauth_secret is not None:
|
||||||
self.oauth_hook = OAuthHook(self.oauth_token, self.oauth_secret)
|
self.oauth_hook = OAuthHook(self.oauth_token, self.oauth_secret, header_auth=True)
|
||||||
self.client = requests.session(hooks={'pre_request': self.oauth_hook})
|
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.
|
# Filter down through the possibilities here - if they have a token, if they're first stage, etc.
|
||||||
|
|
@ -192,7 +203,7 @@ class Twython(object):
|
||||||
self.__dict__[key] = setFunc(key)
|
self.__dict__[key] = setFunc(key)
|
||||||
|
|
||||||
# create stash for last call intel
|
# create stash for last call intel
|
||||||
self._last_call= None
|
self._last_call = None
|
||||||
|
|
||||||
def _constructFunc(self, api_call, **kwargs):
|
def _constructFunc(self, api_call, **kwargs):
|
||||||
# Go through and replace any mustaches that are in our API url.
|
# Go through and replace any mustaches that are in our API url.
|
||||||
|
|
@ -208,55 +219,103 @@ class Twython(object):
|
||||||
if not method in ('get', 'post', 'delete'):
|
if not method in ('get', 'post', 'delete'):
|
||||||
raise TwythonError('Method must be of GET, POST or DELETE')
|
raise TwythonError('Method must be of GET, POST or DELETE')
|
||||||
|
|
||||||
|
content = self._request(url, method=method, params=kwargs)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
def _request(self, url, method='GET', params=None, api_call=None):
|
||||||
|
'''
|
||||||
|
Internal response generator, not sense in repeating the same
|
||||||
|
code twice, right? ;)
|
||||||
|
'''
|
||||||
myargs = {}
|
myargs = {}
|
||||||
|
method = method.lower()
|
||||||
|
|
||||||
if method == 'get':
|
if method == 'get':
|
||||||
url = '%s?%s' % (url, urllib.urlencode(kwargs))
|
url = '%s?%s' % (url, urllib.urlencode(params))
|
||||||
else:
|
else:
|
||||||
myargs = kwargs
|
myargs = params
|
||||||
|
|
||||||
func = getattr(self.client, method)
|
func = getattr(self.client, method)
|
||||||
response = func(url, data=myargs)
|
response = func(url, data=myargs)
|
||||||
content= response.content.decode('utf-8')
|
content = response.content.decode('utf-8')
|
||||||
|
|
||||||
# create stash for last function intel
|
# create stash for last function intel
|
||||||
self._last_call= {
|
self._last_call = {
|
||||||
'api_call':api_call,
|
'api_call': api_call,
|
||||||
'api_error':None,
|
'api_error': None,
|
||||||
'cookies':response.cookies,
|
'cookies': response.cookies,
|
||||||
'error':response.error,
|
'error': response.error,
|
||||||
'headers':response.headers,
|
'headers': response.headers,
|
||||||
'status_code':response.status_code,
|
'status_code': response.status_code,
|
||||||
'url':response.url,
|
'url': response.url,
|
||||||
'content':content,
|
'content': content,
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.status_code not in ( 200 , 304 ):
|
|
||||||
# handle rate limiting first
|
|
||||||
if response.status_code == 420 :
|
|
||||||
raise TwythonAPILimit( "420 || %s || %s" % twitter_http_status_codes[420] )
|
|
||||||
if content:
|
|
||||||
try:
|
|
||||||
as_json= simplejson.loads(content)
|
|
||||||
if 'error' in as_json:
|
|
||||||
self._last_call['api_error']= as_json['error']
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
raise TwythonError( "%s || %s || %s" % ( response.status_code , twitter_http_status_codes[response.status_code][0] , twitter_http_status_codes[response.status_code][1] ) , error_code=response.status_code )
|
|
||||||
|
|
||||||
try:
|
|
||||||
# sometimes this causes an error, and i haven't caught it yet!
|
|
||||||
return simplejson.loads(content)
|
|
||||||
except:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def get_lastfunction_header(self,header):
|
# Python 2.6 `json` will throw a ValueError if it
|
||||||
|
# can't load the string as valid JSON,
|
||||||
|
# `simplejson` will throw simplejson.decoder.JSONDecodeError
|
||||||
|
# But excepting just ValueError will work with both. o.O
|
||||||
|
try:
|
||||||
|
content = simplejson.loads(content)
|
||||||
|
except ValueError:
|
||||||
|
raise TwythonError('Response was not valid JSON, unable to decode.')
|
||||||
|
|
||||||
|
if response.status_code > 304:
|
||||||
|
# Just incase there is no error message, let's set a default
|
||||||
|
error_msg = 'An error occurred processing your request.'
|
||||||
|
if content.get('error') is not None:
|
||||||
|
error_msg = content['error']
|
||||||
|
|
||||||
|
self._last_call = error_msg
|
||||||
|
|
||||||
|
raise TwythonError(error_msg, error_code=response.status_code)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
'''
|
||||||
|
# Dynamic Request Methods
|
||||||
|
Just in case Twitter releases something in their API
|
||||||
|
and a developer wants to implement it on their app, but
|
||||||
|
we haven't gotten around to putting it in Twython yet. :)
|
||||||
|
'''
|
||||||
|
|
||||||
|
def request(self, endpoint, method='GET', params=None, version=1):
|
||||||
|
params = params or {}
|
||||||
|
|
||||||
|
# In case they want to pass a full Twitter URL
|
||||||
|
# i.e. http://search.twitter.com/
|
||||||
|
if endpoint.startswith('http://'):
|
||||||
|
url = endpoint
|
||||||
|
else:
|
||||||
|
url = '%s%s.json' % (self.api_url % version, endpoint)
|
||||||
|
|
||||||
|
content = self._request(url, method=method, params=params, api_call=url)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
def get(self, endpoint, params=None, version=1):
|
||||||
|
params = params or {}
|
||||||
|
return self.request(endpoint, params=params, version=version)
|
||||||
|
|
||||||
|
def post(self, endpoint, params=None, version=1):
|
||||||
|
params = params or {}
|
||||||
|
return self.request(endpoint, 'POST', params=params, version=version)
|
||||||
|
|
||||||
|
def delete(self, endpoint, params=None, version=1):
|
||||||
|
params = params or {}
|
||||||
|
return self.request(endpoint, 'DELETE', params=params, version=version)
|
||||||
|
|
||||||
|
# End Dynamic Request Methods
|
||||||
|
|
||||||
|
def get_lastfunction_header(self, header):
|
||||||
"""
|
"""
|
||||||
get_lastfunction_header(self)
|
get_lastfunction_header(self)
|
||||||
|
|
||||||
returns the header in the last function
|
returns the header in the last function
|
||||||
this must be called after an API call, as it returns header based information.
|
this must be called after an API call, as it returns header based information.
|
||||||
this will return None if the header is not present
|
this will return None if the header is not present
|
||||||
|
|
||||||
most useful for the following header information:
|
most useful for the following header information:
|
||||||
x-ratelimit-limit
|
x-ratelimit-limit
|
||||||
x-ratelimit-remaining
|
x-ratelimit-remaining
|
||||||
|
|
@ -280,7 +339,7 @@ class Twython(object):
|
||||||
request_args = {}
|
request_args = {}
|
||||||
if callback_url:
|
if callback_url:
|
||||||
request_args['oauth_callback'] = callback_url
|
request_args['oauth_callback'] = callback_url
|
||||||
|
|
||||||
method = 'get'
|
method = 'get'
|
||||||
|
|
||||||
func = getattr(self.client, method)
|
func = getattr(self.client, method)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue