Added disconnect to TwythonStreamer, more tests, update example

* Stream and Twython core tests
* Import TwythonStreamError from twython

See more in 2.10.1 section of HISTORY.rst
This commit is contained in:
Mike Helmick 2013-05-24 15:19:19 -04:00
parent 815393cc33
commit c8b1202880
9 changed files with 122 additions and 55 deletions

View file

@ -26,7 +26,7 @@ env:
- PROTECTED_TWITTER_2=TwythonSecure2 - PROTECTED_TWITTER_2=TwythonSecure2
- TEST_TWEET_ID=332992304010899457 - TEST_TWEET_ID=332992304010899457
- TEST_LIST_ID=574 - TEST_LIST_ID=574
script: nosetests -v test_twython:TwythonAPITestCase test_twython:TwythonAuthTestCase --logging-filter="twython" --cover-package="twython" --with-coverage script: nosetests -v test_twython:TwythonAPITestCase test_twython:TwythonAuthTestCase test_twython:TwythonStreamTestCase --logging-filter="twython" --cover-package="twython" --with-coverage
install: pip install -r requirements.txt install: pip install -r requirements.txt
notifications: notifications:
email: false email: false

View file

@ -9,6 +9,8 @@ History
- Fix ``search_gen`` - Fix ``search_gen``
- Fixed ``get_lastfunction_header`` to actually do what its docstring says, returns ``None`` if header is not found - Fixed ``get_lastfunction_header`` to actually do what its docstring says, returns ``None`` if header is not found
- Updated some internal API code, ``__init__`` didn't need to have ``self.auth`` and ``self.headers`` because they were never used anywhere else but the ``__init__`` - Updated some internal API code, ``__init__`` didn't need to have ``self.auth`` and ``self.headers`` because they were never used anywhere else but the ``__init__``
- Added ``disconnect`` method to ``TwythonStreamer``, allowing users to disconnect as they desire
- Updated ``TwythonStreamError`` docstring, also allow importing it from ``twython``
2.10.0 (2013-05-21) 2.10.0 (2013-05-21)
++++++++++++++++++ ++++++++++++++++++

View file

@ -4,6 +4,8 @@ from twython import TwythonStreamer
class MyStreamer(TwythonStreamer): class MyStreamer(TwythonStreamer):
def on_success(self, data): def on_success(self, data):
print data print data
# Want to disconnect after the first result?
# self.disconnect()
def on_error(self, status_code, data): def on_error(self, status_code, data):
print status_code, data print status_code, data

View file

@ -1,7 +1,11 @@
import unittest from twython import(
import os Twython, TwythonStreamer, TwythonError,
TwythonAuthError, TwythonStreamError
)
from twython import Twython, TwythonError, TwythonAuthError import os
import time
import unittest
app_key = os.environ.get('APP_KEY') app_key = os.environ.get('APP_KEY')
app_secret = os.environ.get('APP_SECRET') app_secret = os.environ.get('APP_SECRET')
@ -94,10 +98,17 @@ class TwythonAPITestCase(unittest.TestCase):
def test_search_gen(self): def test_search_gen(self):
'''Test looping through the generator results works, at least once that is''' '''Test looping through the generator results works, at least once that is'''
search = self.api.search_gen('python') search = self.api.search_gen('twitter', count=1)
for result in search: counter = 0
if result: while counter < 2:
break counter += 1
result = search.next()
new_id_str = int(result['id_str'])
if counter == 1:
prev_id_str = new_id_str
time.sleep(1) # Give time for another tweet to come into search
if counter == 2:
self.assertTrue(new_id_str > prev_id_str)
def test_encode(self): def test_encode(self):
'''Test encoding UTF-8 works''' '''Test encoding UTF-8 works'''
@ -480,5 +491,47 @@ class TwythonAPITestCase(unittest.TestCase):
self.api.get_closest_trends(lat='37', long='-122') self.api.get_closest_trends(lat='37', long='-122')
class TwythonStreamTestCase(unittest.TestCase):
def setUp(self):
class MyStreamer(TwythonStreamer):
def on_success(self, data):
self.disconnect()
def on_error(self, status_code, data):
raise TwythonStreamError(data)
def on_delete(self, data):
return
def on_limit(self, data):
return
def on_disconnect(self, data):
return
def on_timeout(self, data):
return
self.api = MyStreamer(app_key, app_secret,
oauth_token, oauth_token_secret)
def test_stream_status_filter(self):
self.api.statuses.filter(track='twitter')
def test_stream_status_sample(self):
self.api.statuses.sample()
def test_stream_status_firehose(self):
self.assertRaises(TwythonStreamError, self.api.statuses.firehose,
track='twitter')
def test_stream_site(self):
self.assertRaises(TwythonStreamError, self.api.site,
follow='twitter')
def test_stream_user(self):
self.api.user(track='twitter')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -22,4 +22,7 @@ __version__ = '2.10.1'
from .twython import Twython from .twython import Twython
from .streaming import TwythonStreamer from .streaming import TwythonStreamer
from .exceptions import TwythonError, TwythonRateLimitError, TwythonAuthError from .exceptions import (
TwythonError, TwythonRateLimitError, TwythonAuthError,
TwythonStreamError
)

View file

@ -2,17 +2,15 @@ from .endpoints import twitter_http_status_codes
class TwythonError(Exception): class TwythonError(Exception):
""" """Generic error class, catch-all for most Twython issues.
Generic error class, catch-all for most Twython issues. Special cases are handled by TwythonAuthError & TwythonRateLimitError.
Special cases are handled by TwythonAuthError & TwythonRateLimitError.
Note: Syntax has changed as of Twython 1.3. To catch these, Note: Syntax has changed as of Twython 1.3. To catch these,
you need to explicitly import them into your code, e.g: you need to explicitly import them into your code, e.g:
from twython import ( from twython import (
TwythonError, TwythonRateLimitError, TwythonAuthError TwythonError, TwythonRateLimitError, TwythonAuthError
) )"""
"""
def __init__(self, msg, error_code=None, retry_after=None): def __init__(self, msg, error_code=None, retry_after=None):
self.error_code = error_code self.error_code = error_code
@ -30,18 +28,16 @@ class TwythonError(Exception):
class TwythonAuthError(TwythonError): class TwythonAuthError(TwythonError):
""" Raised when you try to access a protected resource and it fails due to """Raised when you try to access a protected resource and it fails due to
some issue with your authentication. some issue with your authentication."""
"""
pass pass
class TwythonRateLimitError(TwythonError): class TwythonRateLimitError(TwythonError):
""" Raised when you've hit a rate limit. """Raised when you've hit a rate limit.
The amount of seconds to retry your request in will be appended The amount of seconds to retry your request in will be appended
to the message. to the message."""
"""
def __init__(self, msg, error_code, retry_after=None): def __init__(self, msg, error_code, retry_after=None):
if isinstance(retry_after, int): if isinstance(retry_after, int):
msg = '%s (Retry after %d seconds)' % (msg, retry_after) msg = '%s (Retry after %d seconds)' % (msg, retry_after)
@ -49,5 +45,5 @@ class TwythonRateLimitError(TwythonError):
class TwythonStreamError(TwythonError): class TwythonStreamError(TwythonError):
"""Test""" """Raised when an invalid response from the Stream API is received"""
pass pass

View file

@ -55,41 +55,48 @@ class TwythonStreamer(object):
self.user = StreamTypes.user self.user = StreamTypes.user
self.site = StreamTypes.site self.site = StreamTypes.site
self.connected = False
def _request(self, url, method='GET', params=None): def _request(self, url, method='GET', params=None):
"""Internal stream request handling""" """Internal stream request handling"""
self.connected = True
retry_counter = 0 retry_counter = 0
method = method.lower() method = method.lower()
func = getattr(self.client, method) func = getattr(self.client, method)
def _send(retry_counter): def _send(retry_counter):
try: while self.connected:
if method == 'get':
response = func(url, params=params, timeout=self.timeout)
else:
response = func(url, data=params, timeout=self.timeout)
except requests.exceptions.Timeout:
self.on_timeout()
else:
if response.status_code != 200:
self.on_error(response.status_code, response.content)
if self.retry_count and (self.retry_count - retry_counter) > 0:
time.sleep(self.retry_in)
retry_counter += 1
_send(retry_counter)
return response
response = _send(retry_counter)
for line in response.iter_lines():
if line:
try: try:
self.on_success(json.loads(line)) if method == 'get':
except ValueError: response = func(url, params=params, timeout=self.timeout)
raise TwythonStreamError('Response was not valid JSON, \ else:
unable to decode.') response = func(url, data=params, timeout=self.timeout)
except requests.exceptions.Timeout:
self.on_timeout()
else:
if response.status_code != 200:
self.on_error(response.status_code, response.content)
if self.retry_count and (self.retry_count - retry_counter) > 0:
time.sleep(self.retry_in)
retry_counter += 1
_send(retry_counter)
return response
while self.connected:
response = _send(retry_counter)
for line in response.iter_lines():
if not self.connected:
break
if line:
try:
self.on_success(json.loads(line))
except ValueError:
raise TwythonStreamError('Response was not valid JSON, \
unable to decode.')
def on_success(self, data): def on_success(self, data):
"""Called when data has been successfull received from the stream """Called when data has been successfull received from the stream
@ -161,3 +168,6 @@ class TwythonStreamer(object):
def on_timeout(self): def on_timeout(self):
return return
def disconnect(self):
self.connected = False

View file

@ -61,7 +61,7 @@ class TwythonStreamerTypesStatuses(object):
self.streamer._request(url, params=params) self.streamer._request(url, params=params)
def firehose(self, **params): def firehose(self, **params):
"""Stream statuses/filter """Stream statuses/firehose
Accepted params found at: Accepted params found at:
https://dev.twitter.com/docs/api/1.1/get/statuses/firehose https://dev.twitter.com/docs/api/1.1/get/statuses/firehose

View file

@ -388,11 +388,12 @@ class Twython(object):
yield tweet yield tweet
try: try:
kwargs['page'] = 2 if not 'page' in kwargs else (int(kwargs['page']) + 1) if not 'since_id' in kwargs:
kwargs['since_id'] = (int(content['statuses'][0]['id_str']) + 1)
except (TypeError, ValueError): except (TypeError, ValueError):
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.')
for tweet in self.searchGen(search_query, **kwargs): for tweet in self.search_gen(search_query, **kwargs):
yield tweet yield tweet
@staticmethod @staticmethod