[Python-modules-commits] [tweepy] 01/03: Import tweepy_3.5.0.orig.tar.gz
Ross Gammon
ross-guest at moszumanska.debian.org
Thu Feb 11 21:21:42 UTC 2016
This is an automated email from the git hooks/post-receive script.
ross-guest pushed a commit to branch master
in repository tweepy.
commit 777fef5287218003fecb2d2d55a7b23bdbf3acf6
Author: Ross Gammon <rossgammon at mail.dk>
Date: Thu Feb 11 21:16:39 2016 +0100
Import tweepy_3.5.0.orig.tar.gz
---
.travis.yml | 11 +-
CHANGELOG.md | 2 +
README.md | 2 +-
cassettes/testfailure.json | 287 +++++++++++++++++++++++++++++++++++++++++++++
docs/api.rst | 41 ++++++-
docs/code_snippet.rst | 26 ++++
docs/index.rst | 1 +
docs/parameters.rst | 1 +
examples/streaming.py | 2 +-
tests/test_api.py | 24 ++++
tests/test_streaming.py | 17 +--
tox.ini | 2 +-
tweepy/__init__.py | 2 +-
tweepy/api.py | 15 +--
tweepy/auth.py | 2 -
tweepy/binder.py | 6 +-
tweepy/error.py | 5 +-
tweepy/parsers.py | 21 ++--
tweepy/streaming.py | 25 +++-
19 files changed, 453 insertions(+), 39 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index f492628..a9d773d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,8 +3,11 @@ sudo: false
language: python
python:
+- '2.6'
- '2.7'
+- '3.3'
- '3.4'
+- '3.5'
env:
global:
@@ -43,6 +46,9 @@ script:
after_success:
- if [[ "$TRAVIS_PULL_REQUEST" == "false" ]]; then coveralls; fi
+before_deploy:
+ - pip install twine
+
deploy:
provider: pypi
user: jroesslein
@@ -51,4 +57,7 @@ deploy:
distributions: sdist bdist_wheel
on:
repo: tweepy/tweepy
- branch: release
+ tags: true
+
+matrix:
+ fast_finish: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 013075d..1b2cb21 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,5 @@
+See https://github.com/tweepy/tweepy/releases for change logs.
+
Version 3.3.0
-------------
- Loosen our dependency requirements for Requests (>= 2.4.3)
diff --git a/README.md b/README.md
index b3a93b0..acc95c4 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Github and install it manually:
cd tweepy
python setup.py install
-Python 2.6 and 2.7, 3.3 & 3.4 are supported.
+Python 2.6 and 2.7, 3.3, 3.4 & 3.5 are supported.
Documentation
-------------
diff --git a/cassettes/testfailure.json b/cassettes/testfailure.json
new file mode 100644
index 0000000..96e29e5
--- /dev/null
+++ b/cassettes/testfailure.json
@@ -0,0 +1,287 @@
+{
+ "version": 1,
+ "interactions": [
+ {
+ "request": {
+ "method": "GET",
+ "uri": "https://api.twitter.com:443/1.1/direct_messages.json",
+ "headers": {
+ "Host": [
+ "api.twitter.com"
+ ]
+ },
+ "body": null
+ },
+ "response": {
+ "status": {
+ "message": "Bad Request",
+ "code": 400
+ },
+ "headers": {
+ "x-response-time": [
+ "6"
+ ],
+ "content-type": [
+ "application/json; charset=utf-8"
+ ],
+ "server": [
+ "tsa_b"
+ ],
+ "strict-transport-security": [
+ "max-age=631138519"
+ ],
+ "set-cookie": [
+ "guest_id=v1%3A144655290597218733; Domain=.twitter.com; Path=/; Expires=Thu, 02-Nov-2017 12:15:05 UTC"
+ ],
+ "date": [
+ "Tue, 03 Nov 2015 12:15:05 GMT"
+ ],
+ "x-connection-hash": [
+ "bbad9c628533f023920a78c282b82a2e"
+ ],
+ "content-length": [
+ "62"
+ ]
+ },
+ "body": {
+ "string": "{\"errors\":[{\"code\":215,\"message\":\"Bad Authentication data.\"}]}"
+ }
+ }
+ },
+ {
+ "request": {
+ "method": "GET",
+ "uri": "https://api.twitter.com:443/1.1/direct_messages.json",
+ "headers": {
+ "Cookie": [
+ "guest_id=v1%3A144655290597218733"
+ ],
+ "Host": [
+ "api.twitter.com"
+ ]
+ },
+ "body": null
+ },
+ "response": {
+ "status": {
+ "message": "Bad Request",
+ "code": 400
+ },
+ "headers": {
+ "x-response-time": [
+ "5"
+ ],
+ "content-type": [
+ "application/json; charset=utf-8"
+ ],
+ "server": [
+ "tsa_b"
+ ],
+ "strict-transport-security": [
+ "max-age=631138519"
+ ],
+ "date": [
+ "Tue, 03 Nov 2015 12:15:11 GMT"
+ ],
+ "x-connection-hash": [
+ "bbad9c628533f023920a78c282b82a2e"
+ ],
+ "content-length": [
+ "62"
+ ]
+ },
+ "body": {
+ "string": "{\"errors\":[{\"code\":215,\"message\":\"Bad Authentication data.\"}]}"
+ }
+ }
+ },
+ {
+ "request": {
+ "method": "GET",
+ "uri": "https://api.twitter.com:443/1.1/direct_messages.json",
+ "headers": {
+ "Cookie": [
+ "guest_id=v1%3A144655290597218733"
+ ],
+ "Host": [
+ "api.twitter.com"
+ ]
+ },
+ "body": null
+ },
+ "response": {
+ "status": {
+ "message": "Bad Request",
+ "code": 400
+ },
+ "headers": {
+ "x-response-time": [
+ "3"
+ ],
+ "content-type": [
+ "application/json; charset=utf-8"
+ ],
+ "server": [
+ "tsa_b"
+ ],
+ "strict-transport-security": [
+ "max-age=631138519"
+ ],
+ "date": [
+ "Tue, 03 Nov 2015 12:15:16 GMT"
+ ],
+ "x-connection-hash": [
+ "bbad9c628533f023920a78c282b82a2e"
+ ],
+ "content-length": [
+ "62"
+ ]
+ },
+ "body": {
+ "string": "{\"errors\":[{\"code\":215,\"message\":\"Bad Authentication data.\"}]}"
+ }
+ }
+ },
+ {
+ "request": {
+ "method": "GET",
+ "uri": "https://api.twitter.com:443/1.1/direct_messages.json",
+ "headers": {
+ "Host": [
+ "api.twitter.com"
+ ]
+ },
+ "body": null
+ },
+ "response": {
+ "status": {
+ "message": "Bad Request",
+ "code": 400
+ },
+ "headers": {
+ "x-response-time": [
+ "4"
+ ],
+ "content-type": [
+ "application/json; charset=utf-8"
+ ],
+ "server": [
+ "tsa_b"
+ ],
+ "strict-transport-security": [
+ "max-age=631138519"
+ ],
+ "set-cookie": [
+ "guest_id=v1%3A144655293331152269; Domain=.twitter.com; Path=/; Expires=Thu, 02-Nov-2017 12:15:33 UTC"
+ ],
+ "date": [
+ "Tue, 03 Nov 2015 12:15:33 GMT"
+ ],
+ "x-connection-hash": [
+ "f3384743aac99980194e77031a6b9d66"
+ ],
+ "content-length": [
+ "62"
+ ]
+ },
+ "body": {
+ "string": "{\"errors\":[{\"code\":215,\"message\":\"Bad Authentication data.\"}]}"
+ }
+ }
+ },
+ {
+ "request": {
+ "method": "GET",
+ "uri": "https://api.twitter.com:443/1.1/direct_messages.json",
+ "headers": {
+ "Cookie": [
+ "guest_id=v1%3A144655293331152269"
+ ],
+ "Host": [
+ "api.twitter.com"
+ ]
+ },
+ "body": null
+ },
+ "response": {
+ "status": {
+ "message": "Bad Request",
+ "code": 400
+ },
+ "headers": {
+ "x-response-time": [
+ "4"
+ ],
+ "content-type": [
+ "application/json; charset=utf-8"
+ ],
+ "server": [
+ "tsa_b"
+ ],
+ "strict-transport-security": [
+ "max-age=631138519"
+ ],
+ "date": [
+ "Tue, 03 Nov 2015 12:15:38 GMT"
+ ],
+ "x-connection-hash": [
+ "f3384743aac99980194e77031a6b9d66"
+ ],
+ "content-length": [
+ "62"
+ ]
+ },
+ "body": {
+ "string": "{\"errors\":[{\"code\":215,\"message\":\"Bad Authentication data.\"}]}"
+ }
+ }
+ },
+ {
+ "request": {
+ "method": "GET",
+ "uri": "https://api.twitter.com:443/1.1/direct_messages.json",
+ "headers": {
+ "Cookie": [
+ "guest_id=v1%3A144655293331152269"
+ ],
+ "Host": [
+ "api.twitter.com"
+ ]
+ },
+ "body": null
+ },
+ "response": {
+ "status": {
+ "message": "Bad Request",
+ "code": 400
+ },
+ "headers": {
+ "x-response-time": [
+ "6"
+ ],
+ "content-type": [
+ "application/json; charset=utf-8"
+ ],
+ "server": [
+ "tsa_b"
+ ],
+ "strict-transport-security": [
+ "max-age=631138519"
+ ],
+ "date": [
+ "Tue, 03 Nov 2015 12:15:43 GMT"
+ ],
+ "x-connection-hash": [
+ "f3384743aac99980194e77031a6b9d66"
+ ],
+ "content-length": [
+ "62"
+ ]
+ },
+ "body": {
+ "string": "{\"errors\":[{\"code\":215,\"message\":\"Bad Authentication data.\"}]}"
+ }
+ }
+ }
+ ]
+}
diff --git a/docs/api.rst b/docs/api.rst
index d89f25b..bb7c46a 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -216,7 +216,7 @@ User methods
Direct Message Methods
----------------------
-.. method:: API.direct_messages([since_id], [max_id], [count], [page])
+.. method:: API.direct_messages([since_id], [max_id], [count], [page], [full_text])
Returns direct messages sent to the authenticating user.
@@ -224,10 +224,20 @@ Direct Message Methods
:param max_id: |max_id|
:param count: |count|
:param page: |page|
+ :param full_text: |full_text|
:rtype: list of :class:`DirectMessage` objects
-.. method:: API.sent_direct_messages([since_id], [max_id], [count], [page])
+.. method:: API.get_direct_message([id], [full_text])
+
+ Returns a specific direct message.
+
+ :param id: |id|
+ :param full_text: |full_text|
+ :rtype: :class:`DirectMessage` object
+
+
+.. method:: API.sent_direct_messages([since_id], [max_id], [count], [page], [full_text])
Returns direct messages sent by the authenticating user.
@@ -235,6 +245,7 @@ Direct Message Methods
:param max_id: |max_id|
:param count: |count|
:param page: |page|
+ :param full_text: |full_text|
:rtype: list of :class:`DirectMessage` objects
@@ -777,3 +788,29 @@ Geo Methods
Given *id* of a place, provide more details about that place.
:param id: Valid Twitter ID of a location.
+
+:mod:`tweepy.error` --- Exceptions
+==================================
+
+The exceptions are available in the ``tweepy`` module directly,
+which means ``tweepy.error`` itself does not need to be imported. For
+example, ``tweepy.error.TweepError`` is available as ``tweepy.TweepError``.
+
+.. exception:: TweepError
+
+ The main exception Tweepy uses. Is raised for a number of things.
+
+ When a ``TweepError`` is raised due to an error Twitter responded with,
+ the error code (`as described in the API documentation
+ <https://dev.twitter.com/overview/api/response-codes>`_) can be accessed
+ at ``TweepError.message[0]['code']``. Note, however, that ``TweepError``\ s
+ also may be raised with other things as message (for example plain
+ error reason strings).
+
+.. exception:: RateLimitError
+
+ Is raised when an API method fails due to hitting Twitter's rate
+ limit. Makes for easy handling of the rate limit specifically.
+
+ Inherits from :exc:`TweepError`, so ``except TweepError`` will
+ catch a ``RateLimitError`` too.
diff --git a/docs/code_snippet.rst b/docs/code_snippet.rst
index 3c27d19..aed7d7c 100644
--- a/docs/code_snippet.rst
+++ b/docs/code_snippet.rst
@@ -51,3 +51,29 @@ This snippet will follow every follower of the authenticated user.
for follower in tweepy.Cursor(api.followers).items():
follower.follow()
+
+Handling the rate limit using cursors
+=====================================
+
+Since cursors raise ``RateLimitError``\ s in their ``next()`` method,
+handling them can be done by wrapping the cursor in an iterator.
+
+Running this snippet will print all users you follow that themselves follow
+less than 300 people total - to exclude obvious spambots, for example - and
+will wait for 15 minutes each time it hits the rate limit.
+
+.. code-block :: python
+
+ # In this example, the handler is time.sleep(15 * 60),
+ # but you can of course handle it in any way you want.
+
+ def limit_handled(cursor):
+ while True:
+ try:
+ yield cursor.next()
+ except tweepy.RateLimitError:
+ time.sleep(15 * 60)
+
+ for follower in limit_handled(tweepy.Cursor(api.followers).items()):
+ if follower.friends_count < 300:
+ print follower.screen_name
diff --git a/docs/index.rst b/docs/index.rst
index 005c6ae..0f85108 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -16,6 +16,7 @@ Contents:
code_snippet.rst
cursor_tutorial.rst
api.rst
+ streaming_how_to.rst
Indices and tables
==================
diff --git a/docs/parameters.rst b/docs/parameters.rst
index 5fa1338..313b04f 100644
--- a/docs/parameters.rst
+++ b/docs/parameters.rst
@@ -14,4 +14,5 @@
.. |slug| replace:: the slug name or numerical ID of the list
.. |list_mode| replace:: Whether your list is public or private. Values can be public or private. Lists are public by default if no mode is specified.
.. |list_owner| replace:: the screen name of the owner of the list
+.. |full_text| replace:: A boolean indicating whether or not the full text of a message should be returned. If False the message text returned will be truncated to 140 chars. Defaults to False.
diff --git a/examples/streaming.py b/examples/streaming.py
index e80c530..e90bc5e 100644
--- a/examples/streaming.py
+++ b/examples/streaming.py
@@ -15,7 +15,7 @@ access_token=""
access_token_secret=""
class StdOutListener(StreamListener):
- """ A listener handles tweets are the received from the stream.
+ """ A listener handles tweets that are received from the stream.
This is a basic listener that just prints received tweets to stdout.
"""
diff --git a/tests/test_api.py b/tests/test_api.py
index a43eabe..e38c4a0 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -3,6 +3,7 @@ import random
import shutil
from time import sleep
import os
+from ast import literal_eval
from nose import SkipTest
@@ -32,6 +33,18 @@ class TweepyErrorTests(unittest.TestCase):
class TweepyAPITests(TweepyTestCase):
+ @tape.use_cassette('testfailure.json')
+ def testapierror(self):
+ from tweepy.error import TweepError
+
+ with self.assertRaises(TweepError) as cm:
+ self.api.direct_messages()
+
+ reason, = literal_eval(cm.exception.reason)
+ self.assertEqual(reason['message'], 'Bad Authentication data.')
+ self.assertEqual(reason['code'], 215)
+ self.assertEqual(cm.exception.api_code, 215)
+
# TODO: Actually have some sort of better assertion
@tape.use_cassette('testgetoembed.json')
def testgetoembed(self):
@@ -87,6 +100,17 @@ class TweepyAPITests(TweepyTestCase):
deleted = self.api.destroy_status(id=update.id)
self.assertEqual(deleted.id, update.id)
+ @tape.use_cassette('testupdateanddestroystatus.json')
+ def testupdateanddestroystatuswithoutkwarg(self):
+ # test update, passing text as a positional argument (#554)
+ text = tweet_text if use_replay else 'testing %i' % random.randint(0, 1000)
+ update = self.api.update_status(text)
+ self.assertEqual(update.text, text)
+
+ # test destroy
+ deleted = self.api.destroy_status(id=update.id)
+ self.assertEqual(deleted.id, update.id)
+
@tape.use_cassette('testupdatestatuswithmedia.yaml', serializer='yaml')
def testupdatestatuswithmedia(self):
update = self.api.update_with_media('examples/banner.png', status=tweet_text)
diff --git a/tests/test_streaming.py b/tests/test_streaming.py
index 8b7abf8..008dbb3 100644
--- a/tests/test_streaming.py
+++ b/tests/test_streaming.py
@@ -120,13 +120,13 @@ class TweepyStreamTests(unittest.TestCase):
self.assertEqual(u'Caf\xe9'.encode('utf8'), s.session.params['follow'])
-class TweepyStreamReadBuffer(unittest.TestCase):
+class TweepyStreamReadBufferTests(unittest.TestCase):
- stream = """11\n{id:12345}\n\n24\n{id:23456, test:"blah"}\n"""
+ stream = six.b("""11\n{id:12345}\n\n24\n{id:23456, test:"blah"}\n""")
def test_read_tweet(self):
for length in [1, 2, 5, 10, 20, 50]:
- buf = ReadBuffer(six.StringIO(self.stream), length)
+ buf = ReadBuffer(six.BytesIO(self.stream), length)
self.assertEqual('11\n', buf.read_line())
self.assertEqual('{id:12345}\n', buf.read_len(11))
self.assertEqual('\n', buf.read_line())
@@ -157,13 +157,14 @@ class TweepyStreamReadBuffer(unittest.TestCase):
return ""
# Create a fake stream
- stream = six.StringIO('')
+ stream = six.BytesIO(six.b(''))
# Mock it's read function so it can't be called too many times
mock_read = MagicMock(side_effect=on_read)
try:
- with patch.multiple(stream, create=True, read=mock_read, closed=True):
+ stream.close()
+ with patch.multiple(stream, create=True, read=mock_read):
# Now the stream can't call 'read' more than call_limit times
# and it looks like a requests stream that is closed
buf = ReadBuffer(stream, 50)
@@ -175,14 +176,14 @@ class TweepyStreamReadBuffer(unittest.TestCase):
self.assertEqual(mock_read.call_count, 0)
def test_read_unicode_tweet(self):
- stream = '11\n{id:12345}\n\n23\n{id:23456, test:"\xe3\x81\x93"}\n\n'
+ stream = six.b('11\n{id:12345}\n\n23\n{id:23456, test:"\xe3\x81\x93"}\n\n')
for length in [1, 2, 5, 10, 20, 50]:
- buf = ReadBuffer(six.StringIO(stream), length)
+ buf = ReadBuffer(six.BytesIO(stream), length)
self.assertEqual('11\n', buf.read_line())
self.assertEqual('{id:12345}\n', buf.read_len(11))
self.assertEqual('\n', buf.read_line())
self.assertEqual('23\n', buf.read_line())
- self.assertEqual('{id:23456, test:"\xe3\x81\x93"}\n', buf.read_len(23))
+ self.assertEqual(u'{id:23456, test:"\u3053"}\n', buf.read_len(23))
class TweepyStreamBackoffTests(unittest.TestCase):
diff --git a/tox.ini b/tox.ini
index 36ba97b..61c5413 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,7 +4,7 @@
# and then run "tox" from this directory.
[tox]
-envlist = py26, py27, py33, py34
+envlist = py26, py27, py33, py34, py35
[base]
deps =
diff --git a/tweepy/__init__.py b/tweepy/__init__.py
index 1125d1c..a356369 100644
--- a/tweepy/__init__.py
+++ b/tweepy/__init__.py
@@ -5,7 +5,7 @@
"""
Tweepy Twitter API library
"""
-__version__ = '3.3.0'
+__version__ = '3.5.0'
__author__ = 'Joshua Roesslein'
__license__ = 'MIT'
diff --git a/tweepy/api.py b/tweepy/api.py
index 45c35ee..2216eff 100644
--- a/tweepy/api.py
+++ b/tweepy/api.py
@@ -175,11 +175,12 @@ class API(object):
allowed_param=['id']
)
- def update_status(self, media_ids=None, *args, **kwargs):
+ def update_status(self, *args, **kwargs):
""" :reference: https://dev.twitter.com/rest/reference/post/statuses/update
:allowed_param:'status', 'in_reply_to_status_id', 'lat', 'long', 'source', 'place_id', 'display_coordinates', 'media_ids'
"""
post_data = {}
+ media_ids = kwargs.pop("media_ids", None)
if media_ids is not None:
post_data["media_ids"] = list_to_csv(media_ids)
@@ -391,39 +392,39 @@ class API(object):
@property
def direct_messages(self):
""" :reference: https://dev.twitter.com/rest/reference/get/direct_messages
- :allowed_param:'since_id', 'max_id', 'count'
+ :allowed_param:'since_id', 'max_id', 'count', 'full_text'
"""
return bind_api(
api=self,
path='/direct_messages.json',
payload_type='direct_message', payload_list=True,
- allowed_param=['since_id', 'max_id', 'count'],
+ allowed_param=['since_id', 'max_id', 'count', 'full_text'],
require_auth=True
)
@property
def get_direct_message(self):
""" :reference: https://dev.twitter.com/rest/reference/get/direct_messages/show
- :allowed_param:'id'
+ :allowed_param:'id', 'full_text'
"""
return bind_api(
api=self,
path='/direct_messages/show/{id}.json',
payload_type='direct_message',
- allowed_param=['id'],
+ allowed_param=['id', 'full_text'],
require_auth=True
)
@property
def sent_direct_messages(self):
""" :reference: https://dev.twitter.com/rest/reference/get/direct_messages/sent
- :allowed_param:'since_id', 'max_id', 'count', 'page'
+ :allowed_param:'since_id', 'max_id', 'count', 'page', 'full_text'
"""
return bind_api(
api=self,
path='/direct_messages/sent.json',
payload_type='direct_message', payload_list=True,
- allowed_param=['since_id', 'max_id', 'count', 'page'],
+ allowed_param=['since_id', 'max_id', 'count', 'page', 'full_text'],
require_auth=True
)
diff --git a/tweepy/auth.py b/tweepy/auth.py
index b15434b..c157ad8 100644
--- a/tweepy/auth.py
+++ b/tweepy/auth.py
@@ -85,7 +85,6 @@ class OAuthHandler(AuthHandler):
self.request_token = self._get_request_token(access_type=access_type)
return self.oauth.authorization_url(url)
except Exception as e:
- raise
raise TweepError(e)
def get_access_token(self, verifier=None):
@@ -124,7 +123,6 @@ class OAuthHandler(AuthHandler):
'x_auth_username': username,
'x_auth_password': password})
- print(r.content)
credentials = parse_qs(r.content)
return credentials.get('oauth_token')[0], credentials.get('oauth_token_secret')[0]
except Exception as e:
diff --git a/tweepy/binder.py b/tweepy/binder.py
index ba55570..b96e4d3 100644
--- a/tweepy/binder.py
+++ b/tweepy/binder.py
@@ -217,14 +217,16 @@ def bind_api(**config):
self.api.last_response = resp
if resp.status_code and not 200 <= resp.status_code < 300:
try:
- error_msg = self.parser.parse_error(resp.text)
+ error_msg, api_error_code = \
+ self.parser.parse_error(resp.text)
except Exception:
error_msg = "Twitter error response: status code = %s" % resp.status_code
+ api_error_code = None
if is_rate_limit_error_message(error_msg):
raise RateLimitError(error_msg, resp)
else:
- raise TweepError(error_msg, resp)
+ raise TweepError(error_msg, resp, api_code=api_error_code)
# Parse the response payload
result = self.parser.parse(self, resp.text)
diff --git a/tweepy/error.py b/tweepy/error.py
index 7827029..f7d5894 100644
--- a/tweepy/error.py
+++ b/tweepy/error.py
@@ -9,14 +9,16 @@ import six
class TweepError(Exception):
"""Tweepy exception"""
- def __init__(self, reason, response=None):
+ def __init__(self, reason, response=None, api_code=None):
self.reason = six.text_type(reason)
self.response = response
+ self.api_code = api_code
Exception.__init__(self, reason)
def __str__(self):
return self.reason
+
def is_rate_limit_error_message(message):
"""Check if the supplied error message belongs to a rate limit error."""
return isinstance(message, list) \
@@ -24,6 +26,7 @@ def is_rate_limit_error_message(message):
and 'code' in message[0] \
and message[0]['code'] == 88
+
class RateLimitError(TweepError):
"""Exception for Tweepy hitting the rate limit."""
# RateLimitError has the exact same properties and inner workings
diff --git a/tweepy/parsers.py b/tweepy/parsers.py
index bccb032..6381912 100644
--- a/tweepy/parsers.py
+++ b/tweepy/parsers.py
@@ -21,9 +21,9 @@ class Parser(object):
def parse_error(self, payload):
"""
- Parse the error message from payload.
- If unable to parse the message, throw an exception
- and default error message will be used.
+ Parse the error message and api error code from payload.
+ Return them as an (error_msg, error_code) tuple. If unable to parse the
+ message, throw an exception and default error message will be used.
"""
raise NotImplementedError
@@ -63,11 +63,18 @@ class JSONParser(Parser):
return json
def parse_error(self, payload):
- error = self.json_lib.loads(payload)
- if error.has_key('error'):
- return error['error']
+ error_object = self.json_lib.loads(payload)
+
+ if 'error' in error_object:
+ reason = error_object['error']
+ api_code = error_object.get('code')
else:
- return error['errors']
+ reason = error_object['errors']
+ api_code = [error.get('code') for error in
+ reason if error.get('code')]
+ api_code = api_code[0] if len(api_code) == 1 else api_code
+
+ return reason, api_code
class ModelParser(JSONParser):
diff --git a/tweepy/streaming.py b/tweepy/streaming.py
index c637264..ad7944c 100644
--- a/tweepy/streaming.py
+++ b/tweepy/streaming.py
@@ -7,6 +7,7 @@
from __future__ import absolute_import, print_function
import logging
+import re
import requests
from requests.exceptions import Timeout
from threading import Thread
@@ -148,10 +149,11 @@ class ReadBuffer(object):
use small chunks so it can read the length and the tweet in 2 read calls.
"""
- def __init__(self, stream, chunk_size):
+ def __init__(self, stream, chunk_size, encoding='utf-8'):
self._stream = stream
- self._buffer = ''
+ self._buffer = six.b('')
self._chunk_size = chunk_size
+ self._encoding = encoding
def read_len(self, length):
while not self._stream.closed:
@@ -160,7 +162,13 @@ class ReadBuffer(object):
read_len = max(self._chunk_size, length - len(self._buffer))
self._buffer += self._stream.read(read_len)
- def read_line(self, sep='\n'):
+ def read_line(self, sep=six.b('\n')):
+ """Read the data stream until a given separator is found (default \n)
+
+ :param sep: Separator to read until. Must by of the bytes type (str in python 2,
+ bytes in python 3)
+ :return: The str of the data read until sep
+ """
start = 0
while not self._stream.closed:
loc = self._buffer.find(sep, start)
@@ -173,7 +181,7 @@ class ReadBuffer(object):
def _pop(self, length):
r = self._buffer[:length]
self._buffer = self._buffer[length:]
- return r
+ return r.decode(self._encoding)
class Stream(object):
@@ -290,7 +298,14 @@ class Stream(object):
self.running = False
def _read_loop(self, resp):
- buf = ReadBuffer(resp.raw, self.chunk_size)
+ charset = resp.headers.get('content-type', default='')
+ enc_search = re.search('charset=(?P<enc>\S*)', charset)
+ if enc_search is not None:
+ encoding = enc_search.group('enc')
+ else:
+ encoding = 'utf-8'
+
+ buf = ReadBuffer(resp.raw, self.chunk_size, encoding=encoding)
while self.running and not resp.raw.closed:
length = 0
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/tweepy.git
More information about the Python-modules-commits
mailing list