Merge pull request #241 from ryanmcgrath/dev/cursor

WIP: General cursor (generator) like object for Twython functions, fixes #238
This commit is contained in:
Mike Helmick 2013-07-20 12:01:29 -07:00
commit 52e025a6a2
8 changed files with 181 additions and 34 deletions

View file

@ -14,11 +14,16 @@ from requests.auth import HTTPBasicAuth
from requests_oauthlib import OAuth1, OAuth2
from . import __version__
from .advisory import TwythonDeprecationWarning
from .compat import json, urlencode, parse_qsl, quote_plus, str, is_py2
from .endpoints import EndpointsMixin
from .exceptions import TwythonError, TwythonAuthError, TwythonRateLimitError
from .helpers import _transparent_params
import warnings
warnings.simplefilter('always', TwythonDeprecationWarning) # For Python 2.7 >
class Twython(EndpointsMixin, object):
def __init__(self, app_key=None, app_secret=None, oauth_token=None,
@ -357,13 +362,19 @@ class Twython(EndpointsMixin, object):
)
return '%s?%s' % (api_url, '&'.join(querystring))
def search_gen(self, search_query, **params):
"""Returns a generator of tweets that match a specified query.
def search_gen(self, search_query, **params): # pragma: no cover
warnings.warn(
'This method is deprecated. You should use Twython.cursor instead. [eg. Twython.cursor(Twython.search, q=\'your_query\')]',
TwythonDeprecationWarning,
stacklevel=2
)
return self.cursor(self.search, q=search_query, **params)
Documentation: https://dev.twitter.com/docs/api/1.1/get/search/tweets
def cursor(self, function, **params):
"""Returns a generator for results that match a specified query.
:param search_query: Query you intend to search Twitter for
:param \*\*params: Extra parameters to send with your search request
:param function: Instance of a Twython function (Twython.get_home_timeline, Twython.search)
:param \*\*params: Extra parameters to send with your request (usually parameters excepted by the Twitter API endpoint)
:rtype: generator
Usage::
@ -371,27 +382,46 @@ class Twython(EndpointsMixin, object):
>>> from twython import Twython
>>> twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET)
>>> search = twitter.search_gen('python')
>>> for result in search:
>>> results = twitter.cursor(twitter.search, q='python')
>>> for result in results:
>>> print result
"""
content = self.search(q=search_query, **params)
if not hasattr(function, 'iter_mode'):
raise TwythonError('Unable to create generator for Twython method "%s"' % function.__name__)
if not content.get('statuses'):
content = function(**params)
if not content:
raise StopIteration
for tweet in content['statuses']:
yield tweet
if function.iter_mode == 'cursor' and content['next_cursor_str'] == '0':
raise StopIteration
if hasattr(function, 'iter_key'):
results = content.get(function.iter_key)
else:
results = content
for result in results:
yield result
try:
if not 'since_id' in params:
params['since_id'] = (int(content['statuses'][0]['id_str']) + 1)
if function.iter_mode == 'id':
if not 'max_id' in params:
# Add 1 to the id because since_id and max_id are inclusive
if hasattr(function, 'iter_metadata'):
since_id = content[function.iter_metadata].get('since_id_str')
else:
since_id = content[0]['id_str']
params['since_id'] = (int(since_id) - 1)
elif function.iter_mode == 'cursor':
params['cursor'] = content['next_cursor_str']
except (TypeError, ValueError): # pragma: no cover
raise TwythonError('Unable to generate next page of search results, `page` is not a number.')
for tweet in self.search_gen(search_query, **params):
yield tweet
for result in self.cursor(function, **params):
yield result
@staticmethod
def unicode2utf8(text):

View file

@ -24,6 +24,7 @@ class EndpointsMixin(object):
"""
return self.get('statuses/mentions_timeline', params=params)
get_mentions_timeline.iter_mode = 'id'
def get_user_timeline(self, **params):
"""Returns a collection of the most recent Tweets posted by the user
@ -33,6 +34,7 @@ class EndpointsMixin(object):
"""
return self.get('statuses/user_timeline', params=params)
get_user_timeline.iter_mode = 'id'
def get_home_timeline(self, **params):
"""Returns a collection of the most recent Tweets and retweets
@ -42,6 +44,7 @@ class EndpointsMixin(object):
"""
return self.get('statuses/home_timeline', params=params)
get_home_timeline.iter_mode = 'id'
def retweeted_of_me(self, **params):
"""Returns the most recent tweets authored by the authenticating user
@ -51,6 +54,7 @@ class EndpointsMixin(object):
"""
return self.get('statuses/retweets_of_me', params=params)
retweeted_of_me.iter_mode = 'id'
# Tweets
def get_retweets(self, **params):
@ -119,6 +123,8 @@ class EndpointsMixin(object):
"""
return self.get('statuses/retweeters/ids', params=params)
get_retweeters_ids.iter_mode = 'cursor'
get_retweeters_ids.iter_key = 'ids'
# Search
def search(self, **params):
@ -128,6 +134,9 @@ class EndpointsMixin(object):
"""
return self.get('search/tweets', params=params)
search.iter_mode = 'id'
search.iter_key = 'statuses'
search.iter_metadata = 'search_metadata'
# Direct Messages
def get_direct_messages(self, **params):
@ -137,6 +146,7 @@ class EndpointsMixin(object):
"""
return self.get('direct_messages', params=params)
get_direct_messages.iter_mode = 'id'
def get_sent_messages(self, **params):
"""Returns the 20 most recent direct messages sent by the authenticating user.
@ -145,6 +155,7 @@ class EndpointsMixin(object):
"""
return self.get('direct_messages/sent', params=params)
get_sent_messages.iter_mode = 'id'
def get_direct_message(self, **params):
"""Returns a single direct message, specified by an id parameter.
@ -188,6 +199,8 @@ class EndpointsMixin(object):
"""
return self.get('friends/ids', params=params)
get_friends_ids.iter_mode = 'cursor'
get_friends_ids.iter_key = 'ids'
def get_followers_ids(self, **params):
"""Returns a cursored collection of user IDs for every user
@ -197,6 +210,8 @@ class EndpointsMixin(object):
"""
return self.get('followers/ids', params=params)
get_followers_ids.iter_mode = 'cursor'
get_followers_ids.iter_key = 'ids'
def lookup_friendships(self, **params):
"""Returns the relationships of the authenticating user to the
@ -215,6 +230,8 @@ class EndpointsMixin(object):
"""
return self.get('friendships/incoming', params=params)
get_incoming_friendship_ids.iter_mode = 'cursor'
get_incoming_friendship_ids.iter_key = 'ids'
def get_outgoing_friendship_ids(self, **params):
"""Returns a collection of numeric IDs for every protected user for
@ -224,6 +241,8 @@ class EndpointsMixin(object):
"""
return self.get('friendships/outgoing', params=params)
get_outgoing_friendship_ids.iter_mode = 'cursor'
get_outgoing_friendship_ids.iter_key = 'ids'
def create_friendship(self, **params):
"""Allows the authenticating users to follow the user specified
@ -269,6 +288,8 @@ class EndpointsMixin(object):
"""
return self.get('friends/list', params=params)
get_friends_list.iter_mode = 'cursor'
get_friends_list.iter_key = 'users'
def get_followers_list(self, **params):
"""Returns a cursored collection of user objects for users
@ -278,6 +299,8 @@ class EndpointsMixin(object):
"""
return self.get('followers/list', params=params)
get_followers_list.iter_mode = 'cursor'
get_followers_list.iter_key = 'users'
# Users
def get_account_settings(self, **params):
@ -355,6 +378,8 @@ class EndpointsMixin(object):
"""
return self.get('blocks/list', params=params)
list_blocks.iter_mode = 'cursor'
list_blocks.iter_key = 'users'
def list_block_ids(self, **params):
"""Returns an array of numeric user ids the authenticating user is blocking.
@ -363,6 +388,8 @@ class EndpointsMixin(object):
"""
return self.get('blocks/ids', params=params)
list_block_ids.iter_mode = 'cursor'
list_block_ids.iter_key = 'ids'
def create_block(self, **params):
"""Blocks the specified user from following the authenticating user.
@ -481,6 +508,7 @@ class EndpointsMixin(object):
"""
return self.get('favorites/list', params=params)
get_favorites.iter_mode = 'id'
def destroy_favorite(self, **params):
"""Un-favorites the status specified in the ID parameter as the authenticating user.
@ -514,6 +542,7 @@ class EndpointsMixin(object):
"""
return self.get('lists/statuses', params=params)
get_list_statuses.iter_mode = 'id'
def delete_list_member(self, **params):
"""Removes the specified member from the list.
@ -530,6 +559,8 @@ class EndpointsMixin(object):
"""
return self.get('lists/subscribers', params=params)
get_list_subscribers.iter_mode = 'cursor'
get_list_subscribers.iter_key = 'users'
def subscribe_to_list(self, **params):
"""Subscribes the authenticated user to the specified list.
@ -579,6 +610,8 @@ class EndpointsMixin(object):
"""
return self.get('lists/members', params=params)
get_list_members.iter_mode = 'cursor'
get_list_members.iter_key = 'users'
def add_list_member(self, **params):
"""Add a member to a list.
@ -627,6 +660,8 @@ class EndpointsMixin(object):
"""
return self.get('lists/subscriptions', params=params)
get_list_subscriptions.iter_mode = 'cursor'
get_list_subscriptions.iter_key = 'lists'
def delete_list_members(self, **params):
"""Removes multiple members from a list, by specifying a
@ -644,6 +679,8 @@ class EndpointsMixin(object):
"""
return self.get('lists/ownerships', params=params)
show_owned_lists.iter_mode = 'cursor'
show_owned_lists.iter_key = 'lists'
# Saved Searches
def get_saved_searches(self, **params):