2.1.0 Release

* Removal of oauth2 lib, `requests` has fully taken over. :)
* FIXED: Obtaining auth url with specified callback was broke..
wouldn't give you auth url if you specified a callback url
* Updated requests to pass the headers that are passed in the init, so
User-Agent is once again `Twython Python Twitter Library v2.1.0`
👍 :)
* Catching exception when Stream API doesn't return valid JSON to parse
* Removed `DELETE` method. As of the Spring 2012 clean up, Twitter no
longer supports this method
* Updated `post` internal func to take files as kwarg
* `params - params or {}` only needs to be done in `_request`, just a
lot of redundant code on my part, sorry ;P
* Removed `bulkUserLookup`, there is no need for this to be a special
case, anyone can pass a string of username or user ids and chances are
if they're reading the docs and using this library they'll understand
how to use `lookupUser()` in `twitter_endpoints.py` passing params
provided in the Twitter docs
* Changed internal `oauth_secret` variable to be more consistent with
the keyword arg in the init `oauth_token_secret`
This commit is contained in:
Michael Helmick 2012-05-16 12:09:26 -04:00
parent f2cd0d5284
commit 5e817195ac

View file

@ -9,15 +9,13 @@
""" """
__author__ = "Ryan McGrath <ryan@venodesigns.net>" __author__ = "Ryan McGrath <ryan@venodesigns.net>"
__version__ = "2.0.0" __version__ = "2.1.0"
import urllib import urllib
import re import re
import time
import requests import requests
from requests.auth import OAuth1 from requests.auth import OAuth1
import oauth2 as oauth
try: try:
from urlparse import parse_qsl from urlparse import parse_qsl
@ -109,8 +107,8 @@ class Twython(object):
:param app_key: (optional) Your applications key :param app_key: (optional) Your applications key
:param app_secret: (optional) Your applications secret key :param app_secret: (optional) Your applications secret key
:param oauth_token: (optional) Used with oauth_secret to make authenticated calls :param oauth_token: (optional) Used with oauth_token_secret to make authenticated calls
:param oauth_secret: (optional) Used with oauth_token to make authenticated calls :param oauth_token_secret: (optional) Used with oauth_token to make authenticated calls
:param headers: (optional) Custom headers to send along with the request :param headers: (optional) Custom headers to send along with the request
:param callback_url: (optional) If set, will overwrite the callback url set in your application :param callback_url: (optional) If set, will overwrite the callback url set in your application
""" """
@ -135,9 +133,9 @@ class Twython(object):
if oauth_token is not None: if oauth_token is not None:
self.oauth_token = u'%s' % oauth_token self.oauth_token = u'%s' % oauth_token
self.oauth_secret = None self.oauth_token_secret = None
if oauth_token_secret is not None: if oauth_token_secret is not None:
self.oauth_secret = u'%s' % oauth_token_secret self.oauth_token_secret = u'%s' % oauth_token_secret
self.callback_url = callback_url self.callback_url = callback_url
@ -152,9 +150,9 @@ class Twython(object):
self.auth = OAuth1(self.app_key, self.app_secret, self.auth = OAuth1(self.app_key, self.app_secret,
signature_type='auth_header') signature_type='auth_header')
if self.oauth_token is not None and self.oauth_secret is not None: if self.oauth_token is not None and self.oauth_token_secret is not None:
self.auth = OAuth1(self.app_key, self.app_secret, self.auth = OAuth1(self.app_key, self.app_secret,
self.oauth_token, self.oauth_secret, self.oauth_token, self.oauth_token_secret,
signature_type='auth_header') signature_type='auth_header')
# 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.
@ -182,27 +180,29 @@ class Twython(object):
) )
method = fn['method'].lower() method = fn['method'].lower()
if not method in ('get', 'post', 'delete'): if not method in ('get', 'post'):
raise TwythonError('Method must be of GET, POST or DELETE') raise TwythonError('Method must be of GET or POST')
content = self._request(url, method=method, params=kwargs) content = self._request(url, method=method, params=kwargs)
return content return content
def _request(self, url, method='GET', params=None, api_call=None): def _request(self, url, method='GET', params=None, files=None, api_call=None):
'''Internal response generator, no sense in repeating the same '''Internal response generator, no sense in repeating the same
code twice, right? ;) code twice, right? ;)
''' '''
myargs = {} myargs = {}
method = method.lower() method = method.lower()
params = params or {}
if method == 'get': if method == 'get':
url = '%s?%s' % (url, urllib.urlencode(params)) url = '%s?%s' % (url, urllib.urlencode(params))
else: else:
myargs = params myargs = params
func = getattr(self.client, method) func = getattr(self.client, method)
response = func(url, data=myargs, auth=self.auth) response = func(url, data=myargs, files=files, headers=self.headers, auth=self.auth)
content = response.content.decode('utf-8') content = response.content.decode('utf-8')
# create stash for last function intel # create stash for last function intel
@ -247,31 +247,23 @@ class Twython(object):
we haven't gotten around to putting it in Twython yet. :) we haven't gotten around to putting it in Twython yet. :)
''' '''
def request(self, endpoint, method='GET', params=None, version=1): def request(self, endpoint, method='GET', params=None, files=None, version=1):
params = params or {}
# In case they want to pass a full Twitter URL # In case they want to pass a full Twitter URL
# i.e. http://search.twitter.com/ # i.e. https://search.twitter.com/
if endpoint.startswith('http://') or endpoint.startswith('https://'): if endpoint.startswith('http://') or endpoint.startswith('https://'):
url = endpoint url = endpoint
else: else:
url = '%s/%s.json' % (self.api_url % version, endpoint) url = '%s/%s.json' % (self.api_url % version, endpoint)
content = self._request(url, method=method, params=params, api_call=url) content = self._request(url, method=method, params=params, files=files, api_call=url)
return content return content
def get(self, endpoint, params=None, version=1): def get(self, endpoint, params=None, version=1):
params = params or {}
return self.request(endpoint, params=params, version=version) return self.request(endpoint, params=params, version=version)
def post(self, endpoint, params=None, version=1): def post(self, endpoint, params=None, files=None, version=1):
params = params or {} return self.request(endpoint, 'POST', params=params, files=files, version=version)
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 # End Dynamic Request Methods
@ -297,16 +289,12 @@ class Twython(object):
def get_authentication_tokens(self): def get_authentication_tokens(self):
"""Returns an authorization URL for a user to hit. """Returns an authorization URL for a user to hit.
""" """
callback_url = self.callback_url
request_args = {} request_args = {}
if callback_url: if self.callback_url:
request_args['oauth_callback'] = callback_url request_args['oauth_callback'] = self.callback_url
method = 'get' req_url = self.request_token_url + '?' + urllib.urlencode(request_args)
response = self.client.get(req_url, headers=self.headers, auth=self.auth)
func = getattr(self.client, method)
response = func(self.request_token_url, data=request_args, auth=self.auth)
if response.status_code != 200: if response.status_code != 200:
raise TwythonAuthError("Seems something couldn't be verified with your OAuth junk. Error: %s, Message: %s" % (response.status_code, response.content)) raise TwythonAuthError("Seems something couldn't be verified with your OAuth junk. Error: %s, Message: %s" % (response.status_code, response.content))
@ -322,8 +310,8 @@ class Twython(object):
} }
# Use old-style callback argument if server didn't accept new-style # Use old-style callback argument if server didn't accept new-style
if callback_url and not oauth_callback_confirmed: if self.callback_url and not oauth_callback_confirmed:
auth_url_params['oauth_callback'] = callback_url auth_url_params['oauth_callback'] = self.callback_url
request_tokens['auth_url'] = self.authenticate_url + '?' + urllib.urlencode(auth_url_params) request_tokens['auth_url'] = self.authenticate_url + '?' + urllib.urlencode(auth_url_params)
@ -332,7 +320,7 @@ class Twython(object):
def get_authorized_tokens(self): def get_authorized_tokens(self):
"""Returns authorized tokens after they go through the auth_url phase. """Returns authorized tokens after they go through the auth_url phase.
""" """
response = self.client.get(self.access_token_url, auth=self.auth) response = self.client.get(self.access_token_url, headers=self.headers, auth=self.auth)
authorized_tokens = dict(parse_qsl(response.content)) authorized_tokens = dict(parse_qsl(response.content))
if not authorized_tokens: if not authorized_tokens:
raise TwythonError('Unable to decode authorized tokens.') raise TwythonError('Unable to decode authorized tokens.')
@ -371,34 +359,6 @@ class Twython(object):
def constructApiURL(base_url, params): 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()]) return base_url + "?" + "&".join(["%s=%s" % (Twython.unicode2utf8(key), urllib.quote_plus(Twython.unicode2utf8(value))) for (key, value) in params.iteritems()])
def bulkUserLookup(self, ids=None, screen_names=None, version=1, **kwargs):
""" A method to do bulk user lookups against the Twitter API.
Documentation: https://dev.twitter.com/docs/api/1/get/users/lookup
:ids or screen_names: (required)
:param ids: (optional) A list of integers of Twitter User IDs
:param screen_names: (optional) A list of strings of Twitter Screen Names
:param include_entities: (optional) When set to either true, t or 1,
each tweet will include a node called
"entities,". This node offers a variety of
metadata about the tweet in a discreet structure
e.g x.bulkUserLookup(screen_names=['ryanmcgrath', 'mikehelmick'],
include_entities=1)
"""
if ids is None and screen_names is None:
raise TwythonError('Please supply either a list of ids or \
screen_names for this method.')
if ids is not None:
kwargs['user_id'] = ','.join(map(str, ids))
if screen_names is not None:
kwargs['screen_name'] = ','.join(screen_names)
return self.get('users/lookup', params=kwargs, version=version)
def search(self, **kwargs): def search(self, **kwargs):
""" Returns tweets that match a specified query. """ Returns tweets that match a specified query.
@ -512,44 +472,7 @@ class Twython(object):
**params) **params)
def _media_update(self, url, file_, params=None): def _media_update(self, url, file_, params=None):
params = params or {} return self.post(url, params=params, files=file_)
oauth_params = {
'oauth_timestamp': int(time.time()),
}
#create a fake request with your upload url and parameters
faux_req = oauth.Request(method='POST', url=url, parameters=oauth_params)
#sign the fake request.
signature_method = oauth.SignatureMethod_HMAC_SHA1()
class dotdict(dict):
"""
This is a helper func. because python-oauth2 wants a
dict in dot notation.
"""
def __getattr__(self, attr):
return self.get(attr, None)
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
consumer = {
'key': self.app_key,
'secret': self.app_secret
}
token = {
'key': self.oauth_token,
'secret': self.oauth_secret
}
faux_req.sign_request(signature_method, dotdict(consumer), dotdict(token))
#create a dict out of the fake request signed params
self.headers.update(faux_req.to_header())
req = requests.post(url, data=params, files=file_, headers=self.headers)
return req.content
def getProfileImageUrl(self, username, size='normal', version=1): def getProfileImageUrl(self, username, size='normal', version=1):
"""Gets the URL for the user's profile image. """Gets the URL for the user's profile image.
@ -624,7 +547,10 @@ class Twython(object):
for line in stream.iter_lines(): for line in stream.iter_lines():
if line: if line:
callback(simplejson.loads(line)) try:
callback(simplejson.loads(line))
except ValueError:
raise TwythonError('Response was not valid JSON, unable to decode.')
@staticmethod @staticmethod
def unicode2utf8(text): def unicode2utf8(text):