[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