Merge pull request #333 from greedo/pep8

PEP8
This commit is contained in:
Mike Helmick 2014-07-30 10:33:51 -04:00
commit 1aa2d451d9
13 changed files with 309 additions and 155 deletions

View file

@ -9,7 +9,8 @@ import sys
if len(sys.argv) >= 2: if len(sys.argv) >= 2:
target = sys.argv[1] target = sys.argv[1]
else: else:
target = raw_input("User to follow: ") # For Python 3.x use: target = input("User to follow: ") target = raw_input("User to follow: ")
# For Python 3.x use: target = input("User to follow: ")
# Requires Authentication as of Twitter API v1.1 # Requires Authentication as of Twitter API v1.1
twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET)

View file

@ -8,5 +8,7 @@ except TwythonError as e:
print e print e
for tweet in search_results['statuses']: for tweet in search_results['statuses']:
print 'Tweet from @%s Date: %s' % (tweet['user']['screen_name'].encode('utf-8'), tweet['created_at']) print 'Tweet from @%s Date: %s' % (tweet['user']['screen_nam\
e'].encode('utf-8'),
tweet['created_at'])
print tweet['text'].encode('utf-8'), '\n' print tweet['text'].encode('utf-8'), '\n'

View file

@ -16,5 +16,7 @@ stream = MyStreamer(APP_KEY, APP_SECRET,
OAUTH_TOKEN, OAUTH_TOKEN_SECRET) OAUTH_TOKEN, OAUTH_TOKEN_SECRET)
stream.statuses.filter(track='twitter') stream.statuses.filter(track='twitter')
#stream.user() # Read the authenticated users home timeline (what they see on Twitter) in real-time # stream.user()
# Read the authenticated users home timeline
# (what they see on Twitter) in real-time
# stream.site(follow='twitter') # stream.site(follow='twitter')

View file

@ -29,7 +29,8 @@ setup(
license=open('LICENSE').read(), license=open('LICENSE').read(),
url='https://github.com/ryanmcgrath/twython/tree/master', url='https://github.com/ryanmcgrath/twython/tree/master',
keywords='twitter search api tweet twython stream', keywords='twitter search api tweet twython stream',
description='Actively maintained, pure Python wrapper for the Twitter API. Supports both normal and streaming Twitter APIs', description='Actively maintained, pure Python wrapper for the \
Twitter API. Supports both normal and streaming Twitter APIs',
long_description=open('README.rst').read() + '\n\n' + long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(), open('HISTORY.rst').read(),
include_package_data=True, include_package_data=True,

View file

@ -23,7 +23,8 @@ protected_twitter_2 = os.environ.get('PROTECTED_TWITTER_2', 'TwythonSecure2')
# Test Ids # Test Ids
test_tweet_id = os.environ.get('TEST_TWEET_ID', '318577428610031617') test_tweet_id = os.environ.get('TEST_TWEET_ID', '318577428610031617')
test_list_slug = os.environ.get('TEST_LIST_SLUG', 'team') test_list_slug = os.environ.get('TEST_LIST_SLUG', 'team')
test_list_owner_screen_name = os.environ.get('TEST_LIST_OWNER_SCREEN_NAME', 'twitterapi') test_list_owner_screen_name = os.environ.get('TEST_LIST_OWNER_SCREEN_NAME',
'twitterapi')
test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick should #checkitout. If you can! #thanks Love, @__twython__ https://t.co/67pwRvY6z9', u'in_reply_to_status_id': None, u'id': 349683012054683648, u'favorite_count': 0, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [{u'id': 29251354, u'indices': [45, 57], u'id_str': u'29251354', u'screen_name': u'mikehelmick', u'name': u'Mike Helmick'}, {u'id': 1431865928, u'indices': [104, 116], u'id_str': u'1431865928', u'screen_name': u'__twython__', u'name': u'Twython'}], u'hashtags': [{u'indices': [28, 33], u'text': u'cool'}, {u'indices': [65, 76], u'text': u'checkitout'}, {u'indices': [90, 97], u'text': u'thanks'}], u'urls': [{u'url': u'http://t.co/FCmXyI6VHd', u'indices': [0, 22], u'expanded_url': u'http://google.com', u'display_url': u'google.com'}, {u'url': u'https://t.co/67pwRvY6z9', u'indices': [117, 140], u'expanded_url': u'https://github.com', u'display_url': u'github.com'}]}, u'in_reply_to_screen_name': None, u'id_str': u'349683012054683648', u'retweet_count': 0, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 1431865928, u'verified': False, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'description': {u'urls': []}}, u'followers_count': 1, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'1431865928', u'profile_background_color': u'3D3D3D', u'listed_count': 0, u'profile_background_image_url_https': u'https://si0.twimg.com/images/themes/theme1/bg.png', u'utc_offset': None, u'statuses_count': 2, u'description': u'', u'friends_count': 1, u'location': u'', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'following': False, u'geo_enabled': False, u'profile_background_image_url': u'http://a0.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'__twython__', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Twython', u'notifications': False, u'url': None, u'created_at': u'Thu May 16 01:11:09 +0000 2013', u'contributors_enabled': False, u'time_zone': None, u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'lang': u'en', u'created_at': u'Wed Jun 26 00:18:21 +0000 2013', u'in_reply_to_status_id_str': None, u'place': None} test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick should #checkitout. If you can! #thanks Love, @__twython__ https://t.co/67pwRvY6z9', u'in_reply_to_status_id': None, u'id': 349683012054683648, u'favorite_count': 0, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [{u'id': 29251354, u'indices': [45, 57], u'id_str': u'29251354', u'screen_name': u'mikehelmick', u'name': u'Mike Helmick'}, {u'id': 1431865928, u'indices': [104, 116], u'id_str': u'1431865928', u'screen_name': u'__twython__', u'name': u'Twython'}], u'hashtags': [{u'indices': [28, 33], u'text': u'cool'}, {u'indices': [65, 76], u'text': u'checkitout'}, {u'indices': [90, 97], u'text': u'thanks'}], u'urls': [{u'url': u'http://t.co/FCmXyI6VHd', u'indices': [0, 22], u'expanded_url': u'http://google.com', u'display_url': u'google.com'}, {u'url': u'https://t.co/67pwRvY6z9', u'indices': [117, 140], u'expanded_url': u'https://github.com', u'display_url': u'github.com'}]}, u'in_reply_to_screen_name': None, u'id_str': u'349683012054683648', u'retweet_count': 0, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 1431865928, u'verified': False, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'description': {u'urls': []}}, u'followers_count': 1, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'1431865928', u'profile_background_color': u'3D3D3D', u'listed_count': 0, u'profile_background_image_url_https': u'https://si0.twimg.com/images/themes/theme1/bg.png', u'utc_offset': None, u'statuses_count': 2, u'description': u'', u'friends_count': 1, u'location': u'', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'following': False, u'geo_enabled': False, u'profile_background_image_url': u'http://a0.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'__twython__', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Twython', u'notifications': False, u'url': None, u'created_at': u'Thu May 16 01:11:09 +0000 2013', u'contributors_enabled': False, u'time_zone': None, u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'lang': u'en', u'created_at': u'Wed Jun 26 00:18:21 +0000 2013', u'in_reply_to_status_id_str': None, u'place': None}
test_tweet_html = '<a href="http://t.co/FCmXyI6VHd" class="twython-url">google.com</a> is a <a href="https://twitter.com/search?q=%23cool" class="twython-hashtag">#cool</a> site, lol! <a href="https://twitter.com/mikehelmick" class="twython-mention">@mikehelmick</a> should <a href="https://twitter.com/search?q=%23checkitout" class="twython-hashtag">#checkitout</a>. If you can! <a href="https://twitter.com/search?q=%23thanks" class="twython-hashtag">#thanks</a> Love, <a href="https://twitter.com/__twython__" class="twython-mention">@__twython__</a> <a href="https://t.co/67pwRvY6z9" class="twython-url">github.com</a>' test_tweet_html = '<a href="http://t.co/FCmXyI6VHd" class="twython-url">google.com</a> is a <a href="https://twitter.com/search?q=%23cool" class="twython-hashtag">#cool</a> site, lol! <a href="https://twitter.com/mikehelmick" class="twython-mention">@mikehelmick</a> should <a href="https://twitter.com/search?q=%23checkitout" class="twython-hashtag">#checkitout</a>. If you can! <a href="https://twitter.com/search?q=%23thanks" class="twython-hashtag">#thanks</a> Love, <a href="https://twitter.com/__twython__" class="twython-mention">@__twython__</a> <a href="https://t.co/67pwRvY6z9" class="twython-url">github.com</a>'

View file

@ -315,5 +315,5 @@ class TwythonAPITestCase(unittest.TestCase):
"""Test using expanded url in HTML for Tweet displays full urls""" """Test using expanded url in HTML for Tweet displays full urls"""
tweet_text = self.api.html_for_tweet(test_tweet_object, False) tweet_text = self.api.html_for_tweet(test_tweet_object, False)
# Make sure HTML doesn't contain the display OR expanded url # Make sure HTML doesn't contain the display OR expanded url
self.assertTrue(not 'http://google.com' in tweet_text) self.assertTrue('http://google.com' not in tweet_text)
self.assertTrue(not 'google.com' in tweet_text) self.assertTrue('google.com' not in tweet_text)

View file

@ -20,8 +20,10 @@ class TwythonEndpointsTestCase(unittest.TestCase):
'allow_redirects': False 'allow_redirects': False
} }
# This is so we can hit coverage that Twython sets
# User-Agent for us if none is supplied
oauth2_client_args = { oauth2_client_args = {
'headers': {} # This is so we can hit coverage that Twython sets User-Agent for us if none is supplied 'headers': {}
} }
self.api = Twython(app_key, app_secret, self.api = Twython(app_key, app_secret,
@ -42,7 +44,8 @@ class TwythonEndpointsTestCase(unittest.TestCase):
"""Test returning timeline for authenticated user and random user """Test returning timeline for authenticated user and random user
succeeds""" succeeds"""
self.api.get_user_timeline() # Authenticated User Timeline self.api.get_user_timeline() # Authenticated User Timeline
self.api.get_user_timeline(screen_name='twitter') # Random User Timeline self.api.get_user_timeline(screen_name='twitter')
# Random User Timeline
@unittest.skip('skipping non-updated test') @unittest.skip('skipping non-updated test')
def test_get_protected_user_timeline_following(self): def test_get_protected_user_timeline_following(self):
@ -82,7 +85,8 @@ class TwythonEndpointsTestCase(unittest.TestCase):
@unittest.skip('skipping non-updated test') @unittest.skip('skipping non-updated test')
def test_update_and_destroy_status(self): def test_update_and_destroy_status(self):
"""Test updating and deleting a status succeeds""" """Test updating and deleting a status succeeds"""
status = self.api.update_status(status='Test post just to get deleted :( %s' % int(time.time())) status = self.api.update_status(status='Test post just to get \
deleted :( %s' % int(time.time()))
self.api.destroy_status(id=status['id_str']) self.api.destroy_status(id=status['id_str'])
@unittest.skip('skipping non-updated test') @unittest.skip('skipping non-updated test')
@ -117,7 +121,8 @@ class TwythonEndpointsTestCase(unittest.TestCase):
def test_send_get_and_destroy_direct_message(self): def test_send_get_and_destroy_direct_message(self):
"""Test sending, getting, then destory a direct message succeeds""" """Test sending, getting, then destory a direct message succeeds"""
message = self.api.send_direct_message(screen_name=protected_twitter_1, message = self.api.send_direct_message(screen_name=protected_twitter_1,
text='Hey d00d! %s' % int(time.time())) text='Hey d00d! %s\
' % int(time.time()))
self.api.get_direct_message(id=message['id_str']) self.api.get_direct_message(id=message['id_str'])
self.api.destroy_direct_message(id=message['id_str']) self.api.destroy_direct_message(id=message['id_str'])
@ -127,7 +132,8 @@ class TwythonEndpointsTestCase(unittest.TestCase):
"""Test sending a direct message to someone who doesn't follow you """Test sending a direct message to someone who doesn't follow you
fails""" fails"""
self.assertRaises(TwythonError, self.api.send_direct_message, self.assertRaises(TwythonError, self.api.send_direct_message,
screen_name=protected_twitter_2, text='Yo, man! %s' % int(time.time())) screen_name=protected_twitter_2, text='Yo, man! \
%s' % int(time.time()))
# Friends & Followers # Friends & Followers
@unittest.skip('skipping non-updated test') @unittest.skip('skipping non-updated test')
@ -348,7 +354,8 @@ class TwythonEndpointsTestCase(unittest.TestCase):
the_list = self.api.create_list(name='Stuff %s' % int(time.time())) the_list = self.api.create_list(name='Stuff %s' % int(time.time()))
list_id = the_list['id_str'] list_id = the_list['id_str']
self.api.update_list(list_id=list_id, name='Stuff Renamed %s' % int(time.time())) self.api.update_list(list_id=list_id, name='Stuff Renamed \
%s' % int(time.time()))
screen_names = ['johncena', 'xbox'] screen_names = ['johncena', 'xbox']
# Multi add/delete members # Multi add/delete members
@ -359,7 +366,8 @@ class TwythonEndpointsTestCase(unittest.TestCase):
# Single add/delete member # Single add/delete member
self.api.add_list_member(list_id=list_id, screen_name='justinbieber') self.api.add_list_member(list_id=list_id, screen_name='justinbieber')
self.api.delete_list_member(list_id=list_id, screen_name='justinbieber') self.api.delete_list_member(list_id=list_id,
screen_name='justinbieber')
self.api.delete_list(list_id=list_id) self.api.delete_list(list_id=list_id)

View file

@ -11,8 +11,9 @@ Twython
Twython is a library for Python that wraps the Twitter API. Twython is a library for Python that wraps the Twitter API.
It aims to abstract away all the API endpoints, so that additions to the library It aims to abstract away all the API endpoints, so that
and/or the Twitter API won't cause any overall problems. additions to the library and/or the Twitter API won't
cause any overall problems.
Questions, comments? ryan@venodesigns.net Questions, comments? ryan@venodesigns.net
""" """

View file

@ -15,7 +15,8 @@ only TwythonDeprecationWarnings.
class TwythonDeprecationWarning(DeprecationWarning): class TwythonDeprecationWarning(DeprecationWarning):
"""Custom DeprecationWarning to be raised when methods/variables are being deprecated in Twython. """Custom DeprecationWarning to be raised when methods/variables
Python 2.7 > ignores DeprecationWarning so we want to specifcally bubble up ONLY Twython Deprecation Warnings are being deprecated in Twython. Python 2.7 > ignores DeprecationWarning
so we want to specifcally bubble up ONLY Twython Deprecation Warnings
""" """
pass pass

View file

@ -27,29 +27,41 @@ warnings.simplefilter('always', TwythonDeprecationWarning) # For Python 2.7 >
class Twython(EndpointsMixin, object): class Twython(EndpointsMixin, object):
def __init__(self, app_key=None, app_secret=None, oauth_token=None, def __init__(self, app_key=None, app_secret=None, oauth_token=None,
oauth_token_secret=None, access_token=None, token_type='bearer', oauth_token_secret=None, access_token=None,
oauth_version=1, api_version='1.1', client_args=None, auth_endpoint='authenticate'): token_type='bearer', oauth_version=1, api_version='1.1',
"""Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). client_args=None, auth_endpoint='authenticate'):
"""Instantiates an instance of Twython. Takes optional parameters for
authentication and such (see below).
: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) When using **OAuth 1**, combined with oauth_token_secret to make authenticated calls :param oauth_token: (optional) When using **OAuth 1**, combined with
:param oauth_token_secret: (optional) When using **OAuth 1** combined with oauth_token to make authenticated calls oauth_token_secret to make authenticated calls
:param access_token: (optional) When using **OAuth 2**, provide a valid access token if you have one :param oauth_token_secret: (optional) When using **OAuth 1** combined
:param token_type: (optional) When using **OAuth 2**, provide your token type. Default: bearer with oauth_token to make authenticated calls
:param oauth_version: (optional) Choose which OAuth version to use. Default: 1 :param access_token: (optional) When using **OAuth 2**, provide a
:param api_version: (optional) Choose which Twitter API version to use. Default: 1.1 valid access token if you have one
:param token_type: (optional) When using **OAuth 2**, provide your
token type. Default: bearer
:param oauth_version: (optional) Choose which OAuth version to use.
Default: 1
:param api_version: (optional) Choose which Twitter API version to
use. Default: 1.1
:param client_args: (optional) Accepts some requests Session parameters and some requests Request parameters. :param client_args: (optional) Accepts some requests Session parameters
See http://docs.python-requests.org/en/latest/api/#sessionapi and requests section below it for details. and some requests Request parameters.
See http://docs.python-requests.org/en/latest/api/#sessionapi
and requests section below it for details.
[ex. headers, proxies, verify(SSL verification)] [ex. headers, proxies, verify(SSL verification)]
:param auth_endpoint: (optional) Lets you select which authentication endpoint will use your application. :param auth_endpoint: (optional) Lets you select which authentication
This will allow the application to have DM access if the endpoint is 'authorize'. endpoint will use your application.
This will allow the application to have DM access
if the endpoint is 'authorize'.
Default: authenticate. Default: authenticate.
""" """
# API urls, OAuth urls and API version; needed for hitting that there API. # API urls, OAuth urls and API version; needed for hitting that there
# API.
self.api_version = api_version self.api_version = api_version
self.api_url = 'https://api.twitter.com/%s' self.api_url = 'https://api.twitter.com/%s'
@ -75,16 +87,18 @@ class Twython(EndpointsMixin, object):
self.client_args = client_args or {} self.client_args = client_args or {}
default_headers = {'User-Agent': 'Twython v' + __version__} default_headers = {'User-Agent': 'Twython v' + __version__}
if not 'headers' in self.client_args: if 'headers' not in self.client_args:
# If they didn't set any headers, set our defaults for them # If they didn't set any headers, set our defaults for them
self.client_args['headers'] = default_headers self.client_args['headers'] = default_headers
elif 'User-Agent' not in self.client_args['headers']: elif 'User-Agent' not in self.client_args['headers']:
# If they set headers, but didn't include User-Agent.. set it for them # If they set headers, but didn't include User-Agent.. set
# it for them
self.client_args['headers'].update(default_headers) self.client_args['headers'].update(default_headers)
# Generate OAuth authentication object for the request # Generate OAuth authentication object for the request
# If no keys/tokens are passed to __init__, auth=None allows for # If no keys/tokens are passed to __init__, auth=None allows for
# unauthenticated requests, although I think all v1.1 requests need auth # unauthenticated requests, although I think all v1.1 requests
# need auth
auth = None auth = None
if oauth_version == 1: if oauth_version == 1:
# User Authentication is through OAuth 1 # User Authentication is through OAuth 1
@ -93,12 +107,14 @@ class Twython(EndpointsMixin, object):
auth = OAuth1(self.app_key, self.app_secret) auth = OAuth1(self.app_key, self.app_secret)
if self.app_key is not None and self.app_secret is not None and \ if self.app_key is not None and self.app_secret is not None and \
self.oauth_token is not None and self.oauth_token_secret is not None: self.oauth_token is not None and self.oauth_token_secret is \
not None:
auth = OAuth1(self.app_key, self.app_secret, auth = OAuth1(self.app_key, self.app_secret,
self.oauth_token, self.oauth_token_secret) self.oauth_token, self.oauth_token_secret)
elif oauth_version == 2 and self.access_token: elif oauth_version == 2 and self.access_token:
# Application Authentication is through OAuth 2 # Application Authentication is through OAuth 2
token = {'token_type': token_type, 'access_token': self.access_token} token = {'token_type': token_type,
'access_token': self.access_token}
auth = OAuth2(self.app_key, token=token) auth = OAuth2(self.app_key, token=token)
self.client = requests.Session() self.client = requests.Session()
@ -166,21 +182,26 @@ class Twython(EndpointsMixin, object):
ExceptionType = TwythonError ExceptionType = TwythonError
if response.status_code == 429: if response.status_code == 429:
# Twitter API 1.1, always return 429 when rate limit is exceeded # Twitter API 1.1, always return 429 when
# rate limit is exceeded
ExceptionType = TwythonRateLimitError ExceptionType = TwythonRateLimitError
elif response.status_code == 401 or 'Bad Authentication data' in error_message: elif response.status_code == 401 or 'Bad Authentication data' \
in error_message:
# Twitter API 1.1, returns a 401 Unauthorized or # Twitter API 1.1, returns a 401 Unauthorized or
# a 400 "Bad Authentication data" for invalid/expired app keys/user tokens # a 400 "Bad Authentication data" for invalid/expired
# app keys/user tokens
ExceptionType = TwythonAuthError ExceptionType = TwythonAuthError
raise ExceptionType(error_message, raise ExceptionType(error_message,
error_code=response.status_code, error_code=response.status_code,
retry_after=response.headers.get('retry-after')) retry_after=response.headers.get('retry-\
after'))
try: try:
content = response.json() content = response.json()
except ValueError: except ValueError:
raise TwythonError('Response was not valid JSON. Unable to decode.') raise TwythonError('Response was not valid JSON. \
Unable to decode.')
return content return content
@ -190,7 +211,8 @@ class Twython(EndpointsMixin, object):
error_message = 'An error occurred processing your request.' error_message = 'An error occurred processing your request.'
try: try:
content = response.json() content = response.json()
# {"errors":[{"code":34,"message":"Sorry, that page does not exist"}]} # {"errors":[{"code":34,"message":"Sorry,
# that page does not exist"}]}
error_message = content['errors'][0]['message'] error_message = content['errors'][0]['message']
except ValueError: except ValueError:
# bad json data from Twitter for an error # bad json data from Twitter for an error
@ -204,13 +226,18 @@ class Twython(EndpointsMixin, object):
def request(self, endpoint, method='GET', params=None, version='1.1'): def request(self, endpoint, method='GET', params=None, version='1.1'):
"""Return dict of response received from Twitter's API """Return dict of response received from Twitter's API
:param endpoint: (required) Full url or Twitter API endpoint (e.g. search/tweets) :param endpoint: (required) Full url or Twitter API endpoint
(e.g. search/tweets)
:type endpoint: string :type endpoint: string
:param method: (optional) Method of accessing data, either GET or POST. (default GET) :param method: (optional) Method of accessing data, either
GET or POST. (default GET)
:type method: string :type method: string
:param params: (optional) Dict of parameters (if any) accepted the by Twitter API endpoint you are trying to access (default None) :param params: (optional) Dict of parameters (if any) accepted
the by Twitter API endpoint you are trying to
access (default None)
:type params: dict or None :type params: dict or None
:param version: (optional) Twitter API version to access (default 1.1) :param version: (optional) Twitter API version to access
(default 1.1)
:type version: string :type version: string
:rtype: dict :rtype: dict
@ -223,7 +250,8 @@ class Twython(EndpointsMixin, object):
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,
api_call=url)
return content return content
@ -239,7 +267,8 @@ class Twython(EndpointsMixin, object):
"""Returns a specific header from the last API call """Returns a specific header from the last API call
This will return None if the header is not present This will return None if the header is not present
:param header: (required) The name of the header you want to get the value of :param header: (required) The name of the header you want to get
the value of
Most useful for the following header information: Most useful for the following header information:
x-rate-limit-limit, x-rate-limit-limit,
@ -249,21 +278,31 @@ class Twython(EndpointsMixin, object):
""" """
if self._last_call is None: if self._last_call is None:
raise TwythonError('This function must be called after an API call. It delivers header information.') raise TwythonError('This function must be called after an API call. \
It delivers header information.')
return self._last_call['headers'].get(header, default_return_value) return self._last_call['headers'].get(header, default_return_value)
def get_authentication_tokens(self, callback_url=None, force_login=False, screen_name=''): def get_authentication_tokens(self, callback_url=None, force_login=False,
"""Returns a dict including an authorization URL, ``auth_url``, to direct a user to screen_name=''):
"""Returns a dict including an authorization URL, ``auth_url``, to
direct a user to
:param callback_url: (optional) Url the user is returned to after they authorize your app (web clients only) :param callback_url: (optional) Url the user is returned to after
:param force_login: (optional) Forces the user to enter their credentials to ensure the correct users account is authorized. they authorize your app (web clients only)
:param screen_name: (optional) If forced_login is set OR user is not currently logged in, Prefills the username input box of the OAuth login screen with the given value :param force_login: (optional) Forces the user to enter their
credentials to ensure the correct users
account is authorized.
:param screen_name: (optional) If forced_login is set OR user is
not currently logged in, Prefills the username
input box of the OAuth login screen with the
given value
:rtype: dict :rtype: dict
""" """
if self.oauth_version != 1: if self.oauth_version != 1:
raise TwythonError('This method can only be called when your OAuth version is 1.0.') raise TwythonError('This method can only be called when your \
OAuth version is 1.0.')
request_args = {} request_args = {}
if callback_url: if callback_url:
@ -271,15 +310,18 @@ class Twython(EndpointsMixin, object):
response = self.client.get(self.request_token_url, params=request_args) response = self.client.get(self.request_token_url, params=request_args)
if response.status_code == 401: if response.status_code == 401:
raise TwythonAuthError(response.content, error_code=response.status_code) raise TwythonAuthError(response.content,
error_code=response.status_code)
elif response.status_code != 200: elif response.status_code != 200:
raise TwythonError(response.content, error_code=response.status_code) raise TwythonError(response.content,
error_code=response.status_code)
request_tokens = dict(parse_qsl(response.content.decode('utf-8'))) request_tokens = dict(parse_qsl(response.content.decode('utf-8')))
if not request_tokens: if not request_tokens:
raise TwythonError('Unable to decode request tokens.') raise TwythonError('Unable to decode request tokens.')
oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed') == 'true' oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed') \
== 'true'
auth_url_params = { auth_url_params = {
'oauth_token': request_tokens['oauth_token'], 'oauth_token': request_tokens['oauth_token'],
@ -295,21 +337,28 @@ class Twython(EndpointsMixin, object):
if callback_url and not oauth_callback_confirmed: if callback_url and not oauth_callback_confirmed:
auth_url_params['oauth_callback'] = self.callback_url auth_url_params['oauth_callback'] = self.callback_url
request_tokens['auth_url'] = self.authenticate_url + '?' + urlencode(auth_url_params) request_tokens['auth_url'] = self.authenticate_url + \
'?' + urlencode(auth_url_params)
return request_tokens return request_tokens
def get_authorized_tokens(self, oauth_verifier): def get_authorized_tokens(self, oauth_verifier):
"""Returns a dict of authorized tokens after they go through the :class:`get_authentication_tokens` phase. """Returns a dict of authorized tokens after they go through the
:class:`get_authentication_tokens` phase.
:param oauth_verifier: (required) The oauth_verifier (or a.k.a PIN for non web apps) retrieved from the callback url querystring :param oauth_verifier: (required) The oauth_verifier (or a.k.a PIN
for non web apps) retrieved from the callback url querystring
:rtype: dict :rtype: dict
""" """
if self.oauth_version != 1: if self.oauth_version != 1:
raise TwythonError('This method can only be called when your OAuth version is 1.0.') raise TwythonError('This method can only be called when your \
OAuth version is 1.0.')
response = self.client.get(self.access_token_url, params={'oauth_verifier': oauth_verifier}, headers={'Content-Type': 'application/json'}) response = self.client.get(self.access_token_url,
params={'oauth_verifier': oauth_verifier},
headers={'Content-Type': 'application/\
json'})
if response.status_code == 401: if response.status_code == 401:
try: try:
@ -322,7 +371,8 @@ class Twython(EndpointsMixin, object):
except ValueError: except ValueError:
content = {} content = {}
raise TwythonError(content.get('error', 'Invalid / expired Token'), error_code=response.status_code) raise TwythonError(content.get('error', 'Invalid / expired To \
ken'), error_code=response.status_code)
authorized_tokens = dict(parse_qsl(response.content.decode('utf-8'))) authorized_tokens = dict(parse_qsl(response.content.decode('utf-8')))
if not authorized_tokens: if not authorized_tokens:
@ -331,12 +381,14 @@ class Twython(EndpointsMixin, object):
return authorized_tokens # pragma: no cover return authorized_tokens # pragma: no cover
def obtain_access_token(self): def obtain_access_token(self):
"""Returns an OAuth 2 access token to make OAuth 2 authenticated read-only calls. """Returns an OAuth 2 access token to make OAuth 2 authenticated
read-only calls.
:rtype: string :rtype: string
""" """
if self.oauth_version != 2: if self.oauth_version != 2:
raise TwythonError('This method can only be called when your OAuth version is 2.0.') raise TwythonError('This method can only be called when your \
OAuth version is 2.0.')
data = {'grant_type': 'client_credentials'} data = {'grant_type': 'client_credentials'}
basic_auth = HTTPBasicAuth(self.app_key, self.app_secret) basic_auth = HTTPBasicAuth(self.app_key, self.app_secret)
@ -358,8 +410,10 @@ class Twython(EndpointsMixin, object):
def construct_api_url(api_url, **params): def construct_api_url(api_url, **params):
"""Construct a Twitter API url, encoded, with parameters """Construct a Twitter API url, encoded, with parameters
:param api_url: URL of the Twitter API endpoint you are attempting to construct :param api_url: URL of the Twitter API endpoint you are attempting
:param \*\*params: Parameters that are accepted by Twitter for the endpoint you're requesting to construct
:param \*\*params: Parameters that are accepted by Twitter for the
endpoint you're requesting
:rtype: string :rtype: string
Usage:: Usage::
@ -368,7 +422,8 @@ class Twython(EndpointsMixin, object):
>>> twitter = Twython() >>> twitter = Twython()
>>> api_url = 'https://api.twitter.com/1.1/search/tweets.json' >>> api_url = 'https://api.twitter.com/1.1/search/tweets.json'
>>> constructed_url = twitter.construct_api_url(api_url, q='python', result_type='popular') >>> constructed_url = twitter.construct_api_url(api_url, q='python',
result_type='popular')
>>> print constructed_url >>> print constructed_url
https://api.twitter.com/1.1/search/tweets.json?q=python&result_type=popular https://api.twitter.com/1.1/search/tweets.json?q=python&result_type=popular
@ -384,7 +439,8 @@ class Twython(EndpointsMixin, object):
def search_gen(self, search_query, **params): # pragma: no cover def search_gen(self, search_query, **params): # pragma: no cover
warnings.warn( warnings.warn(
'This method is deprecated. You should use Twython.cursor instead. [eg. Twython.cursor(Twython.search, q=\'your_query\')]', 'This method is deprecated. You should use Twython.cursor instead. \
[eg. Twython.cursor(Twython.search, q=\'your_query\')]',
TwythonDeprecationWarning, TwythonDeprecationWarning,
stacklevel=2 stacklevel=2
) )
@ -393,14 +449,17 @@ class Twython(EndpointsMixin, object):
def cursor(self, function, return_pages=False, **params): def cursor(self, function, return_pages=False, **params):
"""Returns a generator for results that match a specified query. """Returns a generator for results that match a specified query.
:param function: Instance of a Twython function (Twython.get_home_timeline, Twython.search) :param function: Instance of a Twython function
:param \*\*params: Extra parameters to send with your request (usually parameters accepted by the Twitter API endpoint) (Twython.get_home_timeline, Twython.search)
:param \*\*params: Extra parameters to send with your request
(usually parameters accepted by the Twitter API endpoint)
:rtype: generator :rtype: generator
Usage:: Usage::
>>> from twython import Twython >>> from twython import Twython
>>> twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) >>> twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN,
OAUTH_TOKEN_SECRET)
>>> results = twitter.cursor(twitter.search, q='python') >>> results = twitter.cursor(twitter.search, q='python')
>>> for result in results: >>> for result in results:
@ -408,7 +467,8 @@ class Twython(EndpointsMixin, object):
""" """
if not hasattr(function, 'iter_mode'): if not hasattr(function, 'iter_mode'):
raise TwythonError('Unable to create generator for Twython method "%s"' % function.__name__) raise TwythonError('Unable to create generator for Twython \
method "%s"' % function.__name__)
while True: while True:
content = function(**params) content = function(**params)
@ -427,22 +487,26 @@ class Twython(EndpointsMixin, object):
for result in results: for result in results:
yield result yield result
if function.iter_mode == 'cursor' and content['next_cursor_str'] == '0': if function.iter_mode == 'cursor' and \
content['next_cursor_str'] == '0':
raise StopIteration raise StopIteration
try: try:
if function.iter_mode == 'id': if function.iter_mode == 'id':
if not 'max_id' in params: if 'max_id' not in params:
# Add 1 to the id because since_id and max_id are inclusive # Add 1 to the id because since_id and
# max_id are inclusive
if hasattr(function, 'iter_metadata'): if hasattr(function, 'iter_metadata'):
since_id = content[function.iter_metadata].get('since_id_str') since_id = content[function.iter_metadata]\
.get('since_id_str')
else: else:
since_id = content[0]['id_str'] since_id = content[0]['id_str']
params['since_id'] = (int(since_id) - 1) params['since_id'] = (int(since_id) - 1)
elif function.iter_mode == 'cursor': elif function.iter_mode == 'cursor':
params['cursor'] = content['next_cursor_str'] params['cursor'] = content['next_cursor_str']
except (TypeError, ValueError): # pragma: no cover except (TypeError, ValueError): # pragma: no cover
raise TwythonError('Unable to generate next page of search results, `page` is not a number.') raise TwythonError('Unable to generate next page of search \
results, `page` is not a number.')
@staticmethod @staticmethod
def unicode2utf8(text): def unicode2utf8(text):
@ -464,11 +528,14 @@ class Twython(EndpointsMixin, object):
"""Return HTML for a tweet (urls, mentions, hashtags replaced with links) """Return HTML for a tweet (urls, mentions, hashtags replaced with links)
:param tweet: Tweet object from received from Twitter API :param tweet: Tweet object from received from Twitter API
:param use_display_url: Use display URL to represent link (ex. google.com, github.com). Default: True :param use_display_url: Use display URL to represent link
:param use_expanded_url: Use expanded URL to represent link (e.g. http://google.com). Default False (ex. google.com, github.com). Default: True
:param use_expanded_url: Use expanded URL to represent link
(e.g. http://google.com). Default False
If use_expanded_url is True, it overrides use_display_url. If use_expanded_url is True, it overrides use_display_url.
If use_display_url and use_expanded_url is False, short url will be used (t.co/xxxxx) If use_display_url and use_expanded_url is False, short url will
be used (t.co/xxxxx)
""" """
if 'retweeted_status' in tweet: if 'retweeted_status' in tweet:
@ -483,7 +550,8 @@ class Twython(EndpointsMixin, object):
start, end = entity['indices'][0], entity['indices'][1] start, end = entity['indices'][0], entity['indices'][1]
mention_html = '<a href="https://twitter.com/%(screen_name)s" class="twython-mention">@%(screen_name)s</a>' mention_html = '<a href="https://twitter.com/%(screen_name)s" class="twython-mention">@%(screen_name)s</a>'
text = text.replace(tweet['text'][start:end], mention_html % {'screen_name': entity['screen_name']}) text = text.replace(tweet['text'][start:end],
mention_html % {'screen_name': entity['screen_name']})
# Hashtags # Hashtags
for entity in entities['hashtags']: for entity in entities['hashtags']:
@ -495,7 +563,8 @@ class Twython(EndpointsMixin, object):
# Urls # Urls
for entity in entities['urls']: for entity in entities['urls']:
start, end = entity['indices'][0], entity['indices'][1] start, end = entity['indices'][0], entity['indices'][1]
if use_display_url and entity.get('display_url') and not use_expanded_url: if use_display_url and entity.get('display_url') \
and not use_expanded_url:
shown_url = entity['display_url'] shown_url = entity['display_url']
elif use_expanded_url and entity.get('expanded_url'): elif use_expanded_url and entity.get('expanded_url'):
shown_url = entity['expanded_url'] shown_url = entity['expanded_url']
@ -503,6 +572,7 @@ class Twython(EndpointsMixin, object):
shown_url = entity['url'] shown_url = entity['url']
url_html = '<a href="%s" class="twython-url">%s</a>' url_html = '<a href="%s" class="twython-url">%s</a>'
text = text.replace(tweet['text'][start:end], url_html % (entity['url'], shown_url)) text = text.replace(tweet['text'][start:end],
url_html % (entity['url'], shown_url))
return text return text

View file

@ -5,7 +5,8 @@ twython.endpoints
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
This module provides a mixin for a :class:`Twython <Twython>` instance. This module provides a mixin for a :class:`Twython <Twython>` instance.
Parameters that need to be embedded in the API url just need to be passed as a keyword argument. Parameters that need to be embedded in the API url just need to be passed
as a keyword argument.
e.g. Twython.retweet(id=12345) e.g. Twython.retweet(id=12345)
@ -20,7 +21,8 @@ class EndpointsMixin(object):
"""Returns the 20 most recent mentions (tweets containing a users's """Returns the 20 most recent mentions (tweets containing a users's
@screen_name) for the authenticating user. @screen_name) for the authenticating user.
Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline Docs:
https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline
""" """
return self.get('statuses/mentions_timeline', params=params) return self.get('statuses/mentions_timeline', params=params)
@ -63,7 +65,8 @@ class EndpointsMixin(object):
Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweets/%3Aid Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweets/%3Aid
""" """
return self.get('statuses/retweets/%s' % params.get('id'), params=params) return self.get('statuses/retweets/%s' % params.get('id'),
params=params)
def show_status(self, **params): def show_status(self, **params):
"""Returns a single Tweet, specified by the id parameter """Returns a single Tweet, specified by the id parameter
@ -111,7 +114,8 @@ class EndpointsMixin(object):
"""Updates the authenticating user's current status and attaches media """Updates the authenticating user's current status and attaches media
for upload. In other words, it creates a Tweet with a picture attached. for upload. In other words, it creates a Tweet with a picture attached.
Docs: https://dev.twitter.com/docs/api/1.1/post/statuses/update_with_media Docs:
https://dev.twitter.com/docs/api/1.1/post/statuses/update_with_media
""" """
return self.post('statuses/update_with_media', params=params) return self.post('statuses/update_with_media', params=params)
@ -184,7 +188,8 @@ class EndpointsMixin(object):
return self.post('direct_messages/destroy', params=params) return self.post('direct_messages/destroy', params=params)
def send_direct_message(self, **params): def send_direct_message(self, **params):
"""Sends a new direct message to the specified user from the authenticating user. """Sends a new direct message to the specified user from the
authenticating user.
Docs: https://dev.twitter.com/docs/api/1.1/post/direct_messages/new Docs: https://dev.twitter.com/docs/api/1.1/post/direct_messages/new
@ -196,7 +201,8 @@ class EndpointsMixin(object):
"""Returns a collection of user_ids that the currently authenticated """Returns a collection of user_ids that the currently authenticated
user does not want to receive retweets from. user does not want to receive retweets from.
Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/no_retweets/ids Docs:
https://dev.twitter.com/docs/api/1.1/get/friendships/no_retweets/ids
""" """
return self.get('friendships/no_retweets/ids', params=params) return self.get('friendships/no_retweets/ids', params=params)
@ -327,7 +333,8 @@ class EndpointsMixin(object):
requesting user if authentication was successful; returns a 401 status requesting user if authentication was successful; returns a 401 status
code and an error message if not. code and an error message if not.
Docs: https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials Docs:
https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials
""" """
return self.get('account/verify_credentials', params=params) return self.get('account/verify_credentials', params=params)
@ -343,13 +350,15 @@ class EndpointsMixin(object):
def update_delivery_service(self, **params): def update_delivery_service(self, **params):
"""Sets which device Twitter delivers updates to for the authenticating user. """Sets which device Twitter delivers updates to for the authenticating user.
Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_delivery_device Docs:
https://dev.twitter.com/docs/api/1.1/post/account/update_delivery_device
""" """
return self.post('account/update_delivery_device', params=params) return self.post('account/update_delivery_device', params=params)
def update_profile(self, **params): def update_profile(self, **params):
"""Sets values that users are able to set under the "Account" tab of their settings page. """Sets values that users are able to set under the "Account" tab of their
settings page.
Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile
@ -359,7 +368,8 @@ class EndpointsMixin(object):
def update_profile_banner_image(self, **params): # pragma: no cover def update_profile_banner_image(self, **params): # pragma: no cover
"""Updates the authenticating user's profile background image. """Updates the authenticating user's profile background image.
Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_background_image Docs:
https://dev.twitter.com/docs/api/1.1/post/account/update_profile_background_image
""" """
return self.post('account/update_profile_banner', params=params) return self.post('account/update_profile_banner', params=params)
@ -368,7 +378,8 @@ class EndpointsMixin(object):
"""Sets one or more hex values that control the color scheme of the """Sets one or more hex values that control the color scheme of the
authenticating user's profile page on twitter.com. authenticating user's profile page on twitter.com.
Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_colors Docs:
https://dev.twitter.com/docs/api/1.1/post/account/update_profile_colors
""" """
return self.post('account/update_profile_colors', params=params) return self.post('account/update_profile_colors', params=params)
@ -376,13 +387,15 @@ class EndpointsMixin(object):
def update_profile_image(self, **params): # pragma: no cover def update_profile_image(self, **params): # pragma: no cover
"""Updates the authenticating user's profile image. """Updates the authenticating user's profile image.
Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_image Docs:
https://dev.twitter.com/docs/api/1.1/post/account/update_profile_image
""" """
return self.post('account/update_profile_image', params=params) return self.post('account/update_profile_image', params=params)
def list_blocks(self, **params): def list_blocks(self, **params):
"""Returns a collection of user objects that the authenticating user is blocking. """Returns a collection of user objects that the authenticating user
is blocking.
Docs: https://dev.twitter.com/docs/api/1.1/get/blocks/list Docs: https://dev.twitter.com/docs/api/1.1/get/blocks/list
@ -410,7 +423,8 @@ class EndpointsMixin(object):
return self.post('blocks/create', params=params) return self.post('blocks/create', params=params)
def destroy_block(self, **params): def destroy_block(self, **params):
"""Un-blocks the user specified in the ID parameter for the authenticating user. """Un-blocks the user specified in the ID parameter for the
authenticating user.
Docs: https://dev.twitter.com/docs/api/1.1/post/blocks/destroy Docs: https://dev.twitter.com/docs/api/1.1/post/blocks/destroy
@ -419,7 +433,8 @@ class EndpointsMixin(object):
def lookup_user(self, **params): def lookup_user(self, **params):
"""Returns fully-hydrated user objects for up to 100 users per request, """Returns fully-hydrated user objects for up to 100 users per request,
as specified by comma-separated values passed to the user_id and/or screen_name parameters. as specified by comma-separated values passed to the user_id and/or
screen_name parameters.
Docs: https://dev.twitter.com/docs/api/1.1/get/users/lookup Docs: https://dev.twitter.com/docs/api/1.1/get/users/lookup
@ -436,7 +451,8 @@ class EndpointsMixin(object):
return self.get('users/show', params=params) return self.get('users/show', params=params)
def search_users(self, **params): def search_users(self, **params):
"""Provides a simple, relevance-based search interface to public user accounts on Twitter. """Provides a simple, relevance-based search interface to public user
accounts on Twitter.
Docs: https://dev.twitter.com/docs/api/1.1/get/users/search Docs: https://dev.twitter.com/docs/api/1.1/get/users/search
@ -463,7 +479,8 @@ class EndpointsMixin(object):
"""Removes the uploaded profile banner for the authenticating user. """Removes the uploaded profile banner for the authenticating user.
Returns HTTP 200 upon success. Returns HTTP 200 upon success.
Docs: https://dev.twitter.com/docs/api/1.1/post/account/remove_profile_banner Docs:
https://dev.twitter.com/docs/api/1.1/post/account/remove_profile_banner
""" """
return self.post('account/remove_profile_banner', params=params) return self.post('account/remove_profile_banner', params=params)
@ -471,13 +488,16 @@ class EndpointsMixin(object):
def update_profile_background_image(self, **params): def update_profile_background_image(self, **params):
"""Uploads a profile banner on behalf of the authenticating user. """Uploads a profile banner on behalf of the authenticating user.
Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_banner Docs:
https://dev.twitter.com/docs/api/1.1/post/account/update_profile_banner
""" """
return self.post('account/update_profile_background_image', params=params) return self.post('account/update_profile_background_image',
params=params)
def get_profile_banner_sizes(self, **params): def get_profile_banner_sizes(self, **params):
"""Returns a map of the available size variations of the specified user's profile banner. """Returns a map of the available size variations of the specified
user's profile banner.
Docs: https://dev.twitter.com/docs/api/1.1/get/users/profile_banner Docs: https://dev.twitter.com/docs/api/1.1/get/users/profile_banner
@ -488,10 +508,12 @@ class EndpointsMixin(object):
def get_user_suggestions_by_slug(self, **params): def get_user_suggestions_by_slug(self, **params):
"""Access the users in a given category of the Twitter suggested user list. """Access the users in a given category of the Twitter suggested user list.
Docs: https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug Docs:
https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug
""" """
return self.get('users/suggestions/%s' % params.get('slug'), params=params) return self.get('users/suggestions/%s' % params.get('slug'),
params=params)
def get_user_suggestions(self, **params): def get_user_suggestions(self, **params):
"""Access to Twitter's suggested user list. """Access to Twitter's suggested user list.
@ -503,16 +525,20 @@ class EndpointsMixin(object):
def get_user_suggestions_statuses_by_slug(self, **params): def get_user_suggestions_statuses_by_slug(self, **params):
"""Access the users in a given category of the Twitter suggested user """Access the users in a given category of the Twitter suggested user
list and return their most recent status if they are not a protected user. list and return their most recent status if they are not a protected
user.
Docs: https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug/members Docs:
https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug/members
""" """
return self.get('users/suggestions/%s/members' % params.get('slug'), params=params) return self.get('users/suggestions/%s/members' % params.get('slug'),
params=params)
# Favorites # Favorites
def get_favorites(self, **params): def get_favorites(self, **params):
"""Returns the 20 most recent Tweets favorited by the authenticating or specified user. """Returns the 20 most recent Tweets favorited by the authenticating
or specified user.
Docs: https://dev.twitter.com/docs/api/1.1/get/favorites/list Docs: https://dev.twitter.com/docs/api/1.1/get/favorites/list
@ -521,7 +547,8 @@ class EndpointsMixin(object):
get_favorites.iter_mode = 'id' get_favorites.iter_mode = 'id'
def destroy_favorite(self, **params): def destroy_favorite(self, **params):
"""Un-favorites the status specified in the ID parameter as the authenticating user. """Un-favorites the status specified in the ID parameter as the
authenticating user.
Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/destroy Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/destroy
@ -529,7 +556,8 @@ class EndpointsMixin(object):
return self.post('favorites/destroy', params=params) return self.post('favorites/destroy', params=params)
def create_favorite(self, **params): def create_favorite(self, **params):
"""Favorites the status specified in the ID parameter as the authenticating user. """Favorites the status specified in the ID parameter as the
authenticating user.
Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/create Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/create
@ -538,7 +566,8 @@ class EndpointsMixin(object):
# Lists # Lists
def show_lists(self, **params): def show_lists(self, **params):
"""Returns all lists the authenticating or specified user subscribes to, including their own. """Returns all lists the authenticating or specified user subscribes to,
including their own.
Docs: https://dev.twitter.com/docs/api/1.1/get/lists/list Docs: https://dev.twitter.com/docs/api/1.1/get/lists/list
@ -585,7 +614,8 @@ class EndpointsMixin(object):
def subscribe_to_list(self, **params): def subscribe_to_list(self, **params):
"""Subscribes the authenticated user to the specified list. """Subscribes the authenticated user to the specified list.
Docs: https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/create Docs:
https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/create
""" """
return self.post('lists/subscribers/create', params=params) return self.post('lists/subscribers/create', params=params)
@ -601,7 +631,8 @@ class EndpointsMixin(object):
def unsubscribe_from_list(self, **params): def unsubscribe_from_list(self, **params):
"""Unsubscribes the authenticated user from the specified list. """Unsubscribes the authenticated user from the specified list.
Docs: https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/destroy Docs:
https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/destroy
""" """
return self.post('lists/subscribers/destroy', params=params) return self.post('lists/subscribers/destroy', params=params)
@ -610,7 +641,8 @@ class EndpointsMixin(object):
"""Adds multiple members to a list, by specifying a comma-separated """Adds multiple members to a list, by specifying a comma-separated
list of member ids or screen names. list of member ids or screen names.
Docs: https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all Docs:
https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all
""" """
return self.post('lists/members/create_all', params=params) return self.post('lists/members/create_all', params=params)
@ -687,7 +719,8 @@ class EndpointsMixin(object):
"""Removes multiple members from a list, by specifying a """Removes multiple members from a list, by specifying a
comma-separated list of member ids or screen names. comma-separated list of member ids or screen names.
Docs: https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all Docs:
https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all
""" """
return self.post('lists/members/destroy_all', params=params) return self.post('lists/members/destroy_all', params=params)
@ -714,10 +747,12 @@ class EndpointsMixin(object):
def show_saved_search(self, **params): def show_saved_search(self, **params):
"""Retrieve the information for the saved search represented by the given id. """Retrieve the information for the saved search represented by the given id.
Docs: https://dev.twitter.com/docs/api/1.1/get/saved_searches/show/%3Aid Docs:
https://dev.twitter.com/docs/api/1.1/get/saved_searches/show/%3Aid
""" """
return self.get('saved_searches/show/%s' % params.get('id'), params=params) return self.get('saved_searches/show/%s' % params.get('id'),
params=params)
def create_saved_search(self, **params): def create_saved_search(self, **params):
"""Create a new saved search for the authenticated user. """Create a new saved search for the authenticated user.
@ -730,10 +765,12 @@ class EndpointsMixin(object):
def destroy_saved_search(self, **params): def destroy_saved_search(self, **params):
"""Destroys a saved search for the authenticating user. """Destroys a saved search for the authenticating user.
Docs: https://dev.twitter.com/docs/api/1.1/post/saved_searches/destroy/%3Aid Docs:
https://dev.twitter.com/docs/api/1.1/post/saved_searches/destroy/%3Aid
""" """
return self.post('saved_searches/destroy/%s' % params.get('id'), params=params) return self.post('saved_searches/destroy/%s' % params.get('id'),
params=params)
# Places & Geo # Places & Geo
def get_geo_info(self, **params): def get_geo_info(self, **params):
@ -861,7 +898,8 @@ class EndpointsMixin(object):
"""Returns the current rate limits for methods belonging to the """Returns the current rate limits for methods belonging to the
specified resource families. specified resource families.
Docs: https://dev.twitter.com/docs/api/1.1/get/application/rate_limit_status Docs:
https://dev.twitter.com/docs/api/1.1/get/application/rate_limit_status
""" """
return self.get('application/rate_limit_status', params=params) return self.get('application/rate_limit_status', params=params)
@ -871,16 +909,32 @@ class EndpointsMixin(object):
TWITTER_HTTP_STATUS_CODE = { TWITTER_HTTP_STATUS_CODE = {
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 \
401: ('Unauthorized', 'Authentication credentials were missing or incorrect.'), error message will explain why. This is the status code \
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.'), will be returned during rate limiting.'),
404: ('Not Found', 'The URI requested is invalid or the resource requested, such as a user, does not exists.'), 401: ('Unauthorized', 'Authentication credentials were missing \
406: ('Not Acceptable', 'Returned by the Search API when an invalid format is specified in the request.'), or incorrect.'),
410: ('Gone', 'This resource is gone. Used to indicate that an API endpoint has been turned off.'), 403: ('Forbidden', 'The request is understood, but it has been \
422: ('Unprocessable Entity', 'Returned when an image uploaded to POST account/update_profile_banner is unable to be processed.'), refused. An accompanying error message will explain why. \
429: ('Too Many Requests', 'Returned in API v1.1 when a request cannot be served due to the application\'s rate limit having been exhausted for the resource.'), This code is used when requests are being denied due to \
500: ('Internal Server Error', 'Something is broken. Please post to the group so the Twitter team can investigate.'), update limits.'),
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.'),
410: ('Gone', 'This resource is gone. Used to indicate that an \
API endpoint has been turned off.'),
422: ('Unprocessable Entity', 'Returned when an image uploaded to \
POST account/update_profile_banner is unable to be processed.'),
429: ('Too Many Requests', 'Returned in API v1.1 when a request cannot \
be served due to the application\'s rate limit having been \
exhausted for the resource.'),
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 \
504: ('Gateway Timeout', 'The Twitter servers are up, but the request couldn\'t be serviced due to some failure within our stack. Try again later.'), with requests. Try again later.'),
504: ('Gateway Timeout', 'The Twitter servers are up, but the request \
couldn\'t be serviced due to some failure within our stack. Try \
again later.'),
} }

View file

@ -38,8 +38,11 @@ class TwythonStreamer(object):
retired retired
:param retry_in: (optional) Amount of time (in secs) the previous :param retry_in: (optional) Amount of time (in secs) the previous
API call should be tried again API call should be tried again
:param client_args: (optional) Accepts some requests Session parameters and some requests Request parameters. :param client_args: (optional) Accepts some requests Session
See http://docs.python-requests.org/en/latest/api/#sessionapi and requests section below it for details. parameters and some requests Request parameters.
See
http://docs.python-requests.org/en/latest/api/#sessionapi
and requests section below it for details.
[ex. headers, proxies, verify(SSL verification)] [ex. headers, proxies, verify(SSL verification)]
:param handlers: (optional) Array of message types for which :param handlers: (optional) Array of message types for which
corresponding handlers will be called corresponding handlers will be called
@ -53,11 +56,12 @@ class TwythonStreamer(object):
self.client_args = client_args or {} self.client_args = client_args or {}
default_headers = {'User-Agent': 'Twython Streaming v' + __version__} default_headers = {'User-Agent': 'Twython Streaming v' + __version__}
if not 'headers' in self.client_args: if 'headers' not in self.client_args:
# If they didn't set any headers, set our defaults for them # If they didn't set any headers, set our defaults for them
self.client_args['headers'] = default_headers self.client_args['headers'] = default_headers
elif 'User-Agent' not in self.client_args['headers']: elif 'User-Agent' not in self.client_args['headers']:
# If they set headers, but didn't include User-Agent.. set it for them # If they set headers, but didn't include User-Agent..
# set it for them
self.client_args['headers'].update(default_headers) self.client_args['headers'].update(default_headers)
self.client_args['timeout'] = timeout self.client_args['timeout'] = timeout
@ -87,7 +91,8 @@ class TwythonStreamer(object):
self.connected = False self.connected = False
self.handlers = handlers if handlers else ['delete', 'limit', 'disconnect'] self.handlers = handlers if handlers else \
['delete', 'limit', 'disconnect']
self.chunk_size = chunk_size self.chunk_size = chunk_size
@ -103,7 +108,8 @@ class TwythonStreamer(object):
def _send(retry_counter): def _send(retry_counter):
requests_args = {} requests_args = {}
for k, v in self.client_args.items(): for k, v in self.client_args.items():
# Maybe this should be set as a class variable and only done once? # Maybe this should be set as a class
# variable and only done once?
if k in ('timeout', 'allow_redirects', 'verify'): if k in ('timeout', 'allow_redirects', 'verify'):
requests_args[k] = v requests_args[k] = v
@ -121,7 +127,8 @@ class TwythonStreamer(object):
if response.status_code != 200: if response.status_code != 200:
self.on_error(response.status_code, response.content) self.on_error(response.status_code, response.content)
if self.retry_count and (self.retry_count - retry_counter) > 0: if self.retry_count and \
(self.retry_count - retry_counter) > 0:
time.sleep(self.retry_in) time.sleep(self.retry_in)
retry_counter += 1 retry_counter += 1
_send(retry_counter) _send(retry_counter)
@ -140,13 +147,19 @@ class TwythonStreamer(object):
line = line.decode('utf-8') line = line.decode('utf-8')
data = json.loads(line) data = json.loads(line)
except ValueError: # pragma: no cover except ValueError: # pragma: no cover
self.on_error(response.status_code, 'Unable to decode response, not valid JSON.') self.on_error(response.status_code,
'Unable to decode response, \
not valid JSON.')
else: else:
if self.on_success(data): # pragma: no cover if self.on_success(data): # pragma: no cover
for message_type in self.handlers: for message_type in self.handlers:
if message_type in data: if message_type in data:
handler = getattr(self, 'on_' + message_type, None) handler = getattr(self,
if handler and callable(handler) and not handler(data.get(message_type)): 'on_' + message_type,
None)
if handler \
and callable(handler) \
and not handler(data.get(message_type)):
break break
response.close() response.close()