[Python-modules-commits] [tweepy] 01/02: Imported Upstream version 3.4.0

Carl Chenet chaica-guest at moszumanska.debian.org
Tue Oct 27 14:25:47 UTC 2015


This is an automated email from the git hooks/post-receive script.

chaica-guest pushed a commit to branch master
in repository tweepy.

commit c0d857770a294262a6c2894aa2689419c2e0d1ed
Author: Carl Chenet <chaica at ohmytux.com>
Date:   Mon Oct 26 17:51:54 2015 +0100

    Imported Upstream version 3.4.0
---
 .coveragerc               |   5 ++
 .travis.yml               |   2 +
 CHANGELOG.md              |  21 +++++++-
 CONTRIBUTORS              |   3 ++
 README.md                 |  10 ++--
 docs/api.rst              |  74 ++++++++++++----------------
 docs/auth_tutorial.rst    |   9 ++--
 docs/getting_started.rst  |   4 +-
 docs/streaming_how_to.rst | 113 +++++++++++++++++++++++++++++++++++++++++++
 examples/oauth.py         |   2 +-
 requirements.txt          |   6 +--
 setup.py                  |   4 +-
 tests/test_streaming.py   |  51 ++++++++++++++++++++
 tweepy/__init__.py        |  10 ++--
 tweepy/api.py             | 120 +++++++++++++++++++++++++++++-----------------
 tweepy/binder.py          |  14 ++++--
 tweepy/error.py           |  14 +++++-
 tweepy/models.py          |  11 +++++
 tweepy/streaming.py       |  70 +++++++++++++++------------
 tweepy/utils.py           |   6 ---
 20 files changed, 396 insertions(+), 153 deletions(-)

diff --git a/.coveragerc b/.coveragerc
index aad5cc3..89f9f1d 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,2 +1,7 @@
 [run]
 source = tweepy
+
+[report]
+omit =
+  */python?.?/*
+  */site-packages/*
diff --git a/.travis.yml b/.travis.yml
index 3db95b5..f492628 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,5 @@
+sudo: false
+
 language: python
 
 python:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 97229e9..013075d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,22 @@
-Version 3.1
------------
+Version 3.3.0
+-------------
+  - Loosen our dependency requirements for Requests (>= 2.4.3)
+  - Fix issue with streams freezing up on Python 3 (Issue #556)
+  - Add keep_alive() callback to StreamListener when keep alive messages arrive
+  - Fix issue with stream session headers not being used when restarting connection
+  - Fix issue with streams getting stuck in a loop when connection dies. (PR #561)
+
+Version 3.2.0
+-------------
+  - Remove deprecated trends methods.
+  - Fix tweepy.debug() to work in Python 3.
+  - Fixed issue #529 - StreamListener language filter stopped working.
+  - Add Documentation Page for streaming.
+  - Add media/upload endpoint.
+  - Add media_ids parameter to update_status().
+
+Version 3.1.0
+-------------
   - Allow specifying your own ssl certificates for streaming client.
   - Distribute Python Wheels instead of dumb binaries.
   - Fix cursor invocation, passing args to underlying method. (https://github.com/tweepy/tweepy/issues/515)
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index f99fbd2..edaee86 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -13,6 +13,7 @@ Ferenc Szalai
 Gergely Imreh
 Guan Yang
 Ivo Wetzel
+Jared Stefanowicz
 James Rowe
 Jenny Loomis
 Johannes Faigle
@@ -32,3 +33,5 @@ Jeff Hull (@jsh2134)
 Mike (mikeandmore)
 Kohei YOSHIDA
 Mark Smith (@judy2k)
+Steven Skoczen (@skoczen)
+Samuel (@obskyr)
diff --git a/README.md b/README.md
index c09a891..b3a93b0 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,11 @@
 Tweepy: Twitter for Python!
 ======
-[![Build Status](https://travis-ci.org/tweepy/tweepy.png?branch=master)](https://travis-ci.org/tweepy/tweepy)
-[![Downloads](https://pypip.in/d/tweepy/badge.png)](https://crate.io/packages/tweepy) [![Downloads](https://pypip.in/v/tweepy/badge.png)](https://crate.io/packages/tweepy)
-[![Coverage Status](https://coveralls.io/repos/tweepy/tweepy/badge.png?branch=master)](https://coveralls.io/r/tweepy/tweepy?branch=master)
+
+[![Join the chat at https://gitter.im/tweepy/tweepy](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tweepy/tweepy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![Build Status](http://img.shields.io/travis/tweepy/tweepy/master.svg?style=flat)](https://travis-ci.org/tweepy/tweepy)
+[![Documentation Status](http://img.shields.io/badge/docs-v3.1.0-brightgreen.svg?style=flat)](http://docs.tweepy.org)
+[![Downloads](http://img.shields.io/pypi/dm/tweepy.svg?style=flat)](https://crate.io/packages/tweepy) [![Version](http://img.shields.io/pypi/v/tweepy.svg?style=flat)](https://crate.io/packages/tweepy)
+[![Coverage Status](https://img.shields.io/coveralls/tweepy/tweepy/master.svg?style=flat)](https://coveralls.io/r/tweepy/tweepy?branch=master)
 
 Installation
 ------------
@@ -29,4 +32,3 @@ Community
 ---------
   - [Discussion Forum](http://discuss.tweepy.org)
   - IRC Chat (Freenode.net #tweepy)
-
diff --git a/docs/api.rst b/docs/api.rst
index 8b096bb..d89f25b 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -174,7 +174,7 @@ User methods
    :rtype: :class:`User` object
 
 
-.. method::API.friends([id/user_id/screen_name], [cursor])
+.. method::API.friends([id/user_id/screen_name], [cursor], [skip_status], [include_user_entities])
 
    Returns an user's friends ordered in which they were added 100 at a time. If no user is specified it defaults to the authenticated user.
 
@@ -182,6 +182,8 @@ User methods
    :param user_id: |user_id|
    :param screen_name: |screen_name|
    :param cursor: |cursor|
+   :param skip_status: |skip_status|
+   :param include_user_entities: |include_user_entities|
    :rtype: list of :class:`User` objects
 
 
@@ -540,43 +542,6 @@ Help Methods
    :rtype: list of :class:`SearchResult` objects
 
 
-.. method:: API.trends()
-
-   Returns the top ten topics that are currently trending on Twitter. The
-   response includes the time of the request, the name of each trend, and
-   the url to the Twitter Search results page for that topic.
-
-   :rtype: :class:`JSON` object
-
-
-.. method:: API.trends_current([exclude])
-
-   Returns the current top 10 trending topics on Twitter. The response
-   includes the time of the request, the name of each trending topic, and
-   query used on Twitter Search results page for that topic.
-
-   :param exclude: |exclude|
-   :rtype: :class:`JSON` object
-
-
-.. method:: API.trends_daily([date], [exclude])
-
-   Returns the top 20 trending topics for each hour in a given day.
-
-   :param date: |date|
-   :param exclude: |exclude|
-   :rtype: :class:`JSON` object
-
-
-.. method:: API.trends_weekly([date], [exclude])
-
-   Returns the top 30 trending topics for each day in a given week.
-
-   :param date: |date|
-   :param exclude: |exclude|
-   :rtype: :class:`JSON` object
-
-
 List Methods
 ------------
 
@@ -738,15 +703,38 @@ List Methods
    :rtype: :class:`User` object if user is subscribed to the list, otherwise False.
 
 
-Local Trends Methods
+Trends Methods
 --------------------
 
-.. method:: API.trends_available([lat], [long])
+.. method:: API.trends_available()
+
+   Returns the locations that Twitter has trending topic information for. The response is an array of "locations" that encode the location's WOEID (a Yahoo! Where On Earth ID) and some other human-readable information such as a canonical name and country the location belongs in.
+
+   :rtype: :class:`JSON` object
+
+
+.. method:: API.trends_place(id, [exclude])
+
+   Returns the top 10 trending topics for a specific WOEID, if trending information is available for it.
+
+   The response is an array of “trend” objects that encode the name of the trending topic, the query parameter that can be used to search for the topic on Twitter Search, and the Twitter Search URL.
+
+   This information is cached for 5 minutes. Requesting more frequently than that will not return any more data, and will count against your rate limit usage.
+
+   :param id: The Yahoo! Where On Earth ID of the location to return trending information for. Global information is available by using 1 as the WOEID.
+   :param exclude: Setting this equal to hashtags will remove all hashtags from the trends list.
+   :rtype: :class:`JSON` object
+
+.. method:: API.trends_closest(lat, long)
+
+   Returns the locations that Twitter has trending topic information for, closest to a specified location.
+
+   The response is an array of “locations” that encode the location’s WOEID and some other human-readable information such as a canonical name and country the location belongs in.
 
-   Returns the locations that Twitter has trending topic information for. The response is an array of "locations" that encode the location's WOEID (a Yahoo! Where On Earth ID) and some other human-readable information such as a canonical name and country the location belongs in. [Coming soon]
+   A WOEID is a Yahoo! Where On Earth ID.
 
-   :param lat: If passed in conjunction with long, then the available trend locations will be sorted by distance to the lat and long passed in.  The sort is nearest to furthest.
-   :param long: See lat.
+   :param lat: If provided with a long parameter the available trend locations will be sorted by distance, nearest to furthest, to the co-ordinate pair. The valid ranges for longitude is -180.0 to +180.0 (West is negative, East is positive) inclusive.
+   :param long: If provided with a lat parameter the available trend locations will be sorted by distance, nearest to furthest, to the co-ordinate pair. The valid ranges for longitude is -180.0 to +180.0 (West is negative, East is positive) inclusive.
    :rtype: :class:`JSON` object
 
 
diff --git a/docs/auth_tutorial.rst b/docs/auth_tutorial.rst
index f088444..ed2fc6e 100644
--- a/docs/auth_tutorial.rst
+++ b/docs/auth_tutorial.rst
@@ -65,8 +65,7 @@ request token in the session since we will need it inside the callback
 URL request. Here is a pseudo example of storing the request token in
 a session::
 
-   session.set('request_token', (auth.request_token.key,
-   auth.request_token.secret))
+   session.set('request_token', auth.request_token)
 
 So now we can redirect the user to the URL returned to us earlier from
 the get_authorization_url() method.
@@ -94,7 +93,7 @@ treasure box. To fetch this token we do the following::
    auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
    token = session.get('request_token')
    session.delete('request_token')
-   auth.set_request_token(token[0], token[1])
+   auth.request_token = token
    
    try:
        auth.get_access_token(verifier)
@@ -108,8 +107,8 @@ revokes our application access. To store the access token depends on
 your application. Basically you need to store 2 string values: key and
 secret::
 
-   auth.access_token.key
-   auth.access_token.secret
+   auth.access_token
+   auth.access_token_secret
 
 You can throw these into a database, file, or where ever you store
 your data. To re-build an OAuthHandler from this stored access token
diff --git a/docs/getting_started.rst b/docs/getting_started.rst
index 895679b..abafd8c 100644
--- a/docs/getting_started.rst
+++ b/docs/getting_started.rst
@@ -50,9 +50,9 @@ from Twitter which we can then use inside our application. For example
 the following code returns to us an User model::
 
    # Get the User object for twitter...
-   user = tweepy.api.get_user('twitter')
+   user = api.get_user('twitter')
 
-Models container the data and some helper methods which we can then
+Models contain the data and some helper methods which we can then
 use::
 
    print user.screen_name
diff --git a/docs/streaming_how_to.rst b/docs/streaming_how_to.rst
new file mode 100644
index 0000000..a2bd43d
--- /dev/null
+++ b/docs/streaming_how_to.rst
@@ -0,0 +1,113 @@
+.. _streaming_how_to:
+.. _Twitter Streaming API Documentation: https://dev.twitter.com/streaming/overview
+.. _Twitter Response Codes Documentation: https://dev.twitter.com/overview/api/response-codes
+
+*********************
+Streaming With Tweepy
+*********************
+Tweepy makes it easier to use the twitter streaming api by handling authentication, 
+connection, creating and destroying the session, reading incoming messages, 
+and partially routing messages. 
+
+This page aims to help you get started using Twitter streams with Tweepy 
+by offering a first walk through.  Some features of Tweepy streaming are
+not covered here. See streaming.py in the Tweepy source code. 
+
+API authorization is required to access Twitter streams. 
+Follow the :ref:`auth_tutorial` if you need help with authentication. 
+
+Summary
+=======
+The Twitter streaming API is used to download twitter messages in real 
+time.  It is useful for obtaining a high volume of tweets, or for 
+creating a live feed using a site stream or user stream. 
+See the `Twitter Streaming API Documentation`_.
+
+The streaming api is quite different from the REST api because the
+REST api is used to *pull* data from twitter but the streaming api
+*pushes* messages to a persistent session. This allows the streaming 
+api to download more data in real time than could be done using the
+REST API. 
+
+In Tweepy, an instance of **tweepy.Stream** establishes a streaming 
+session and routes messages to **StreamListener** instance.  The
+**on_data** method of a stream listener receives all messages and
+calls functions according to the message type. The default 
+**StreamListener** can classify most common twitter messages and 
+routes them to appropriately named methods, but these methods are 
+only stubs. 
+
+Therefore using the streaming api has three steps. 
+
+1. Create a class inheriting from **StreamListener**
+
+2. Using that class create a **Stream** object 
+
+3. Connect to the Twitter API using the **Stream**.
+
+
+Step 1: Creating a **StreamListener**
+=====================================
+This simple stream listener prints status text.
+The **on_data** method of Tweepy's **StreamListener** conveniently passes 
+data from statuses to the **on_status** method.
+Create class **MyStreamListener** inheriting from  **StreamListener** 
+and overriding **on_status**.::
+  import tweepy
+  #override tweepy.StreamListener to add logic to on_status
+  class MyStreamListener(tweepy.StreamListener):
+  
+      def on_status(self, status):
+          print(status.text)
+
+Step 2: Creating a **Stream**
+=============================
+We need an api to stream. See :ref:`auth_tutorial` to learn how to get an api object. 
+Once we have an api and a status listener we can create our stream object.::
+
+  myStreamListener = MyStreamListener()
+  myStream = tweepy.Stream(auth = api.auth, listener=myStreamListener())
+
+Step 3: Starting a Stream
+=========================
+A number of twitter streams are available through Tweepy. Most cases 
+will use filter, the user_stream, or the sitestream. 
+For more information on the capabilities and limitations of the different
+streams see `Twitter Streaming API Documentation`_.
+
+In this example we will use **filter** to stream all tweets containing
+the word *python*. The **track** parameter is an array of search terms to stream. ::
+  
+  myStream.filter(track=['python'])
+
+
+A Few More Pointers
+===================
+
+Async Streaming
+---------------
+Streams not terminate unless the connection is closed, blocking the thread. 
+Tweepy offers a convenient **async** parameter on **filter** so the stream will run on a new
+thread. For example ::
+
+  myStream.filter(track=['python'], async=True)
+
+Handling Errors
+---------------
+When using Twitter's streaming API one must be careful of the dangers of 
+rate limiting. If clients exceed a limited number of attempts to connect to the streaming API 
+in a window of time, they will receive error 420.  The amount of time a client has to wait after receiving error 420
+will increase exponentially each time they make a failed attempt. 
+
+Tweepy's **Stream Listener** usefully passes error messages to an **on_error** stub. We can use **on_error** to 
+catch 420 errors and disconnect our stream. ::
+
+  class MyStreamListener(tweepy.StreamListener):
+  
+      def on_error(self, status_code):
+          if status_code == 420:
+              #returning False in on_data disconnects the stream
+              return False
+
+For more information on error codes from the twitter api see `Twitter Response Codes Documentation`_.
+
diff --git a/examples/oauth.py b/examples/oauth.py
index 6f08dab..4035a35 100644
--- a/examples/oauth.py
+++ b/examples/oauth.py
@@ -31,4 +31,4 @@ print(api.me().name)
 # If the application settings are set for "Read and Write" then
 # this line should tweet out the message to your account's
 # timeline. The "Read and Write" setting is on https://dev.twitter.com/apps
-api.update_status('Updating using OAuth authentication via Tweepy!')
+api.update_status(status='Updating using OAuth authentication via Tweepy!')
diff --git a/requirements.txt b/requirements.txt
index 7dd33e0..3bb53b3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,3 @@
-requests==2.4.3
-requests_oauthlib==0.4.1
-six==1.7.3
+requests>=2.4.3
+requests_oauthlib>=0.4.1
+six>=1.7.3
diff --git a/setup.py b/setup.py
index 2de2d05..3b3b807 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #from distutils.core import setup
-import re
+import re, uuid
 from setuptools import setup, find_packages
 from pip.req import parse_requirements
 
@@ -14,7 +14,7 @@ if mo:
 else:
     raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
 
-install_reqs = parse_requirements('requirements.txt')
+install_reqs = parse_requirements('requirements.txt', session=uuid.uuid1())
 reqs = [str(req.req) for req in install_reqs]
 
 setup(name="tweepy",
diff --git a/tests/test_streaming.py b/tests/test_streaming.py
index a87c2fc..8b7abf8 100644
--- a/tests/test_streaming.py
+++ b/tests/test_streaming.py
@@ -133,6 +133,57 @@ class TweepyStreamReadBuffer(unittest.TestCase):
             self.assertEqual('24\n', buf.read_line())
             self.assertEqual('{id:23456, test:"blah"}\n', buf.read_len(24))
 
+    def test_read_empty_buffer(self):
+        """
+        Requests can be closed by twitter.
+        The ReadBuffer should not loop infinitely when this happens.
+        Instead it should return and let the outer _read_loop handle it.
+        """
+
+        # If the test fails, we are in danger of an infinite loop
+        # so we need to do some work to block that from happening
+        class InfiniteLoopException(Exception):
+            pass
+
+        self.called_count = 0
+        call_limit = 5
+        def on_read(chunk_size):
+            self.called_count += 1
+
+            if self.called_count > call_limit:
+                # we have failed
+                raise InfiniteLoopException("Oops, read() was called a bunch of times")
+
+            return ""
+
+        # Create a fake stream
+        stream = six.StringIO('')
+
+        # 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):
+                # 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)
+                buf.read_line("\n")
+        except InfiniteLoopException:
+            self.fail("ReadBuffer.read_line tried to loop infinitely.")
+
+        # The mocked function not have been called at all since the stream looks closed
+        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'
+        for length in [1, 2, 5, 10, 20, 50]:
+            buf = ReadBuffer(six.StringIO(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))
+
 
 class TweepyStreamBackoffTests(unittest.TestCase):
     def setUp(self):
diff --git a/tweepy/__init__.py b/tweepy/__init__.py
index 85c4b5b..1125d1c 100644
--- a/tweepy/__init__.py
+++ b/tweepy/__init__.py
@@ -5,12 +5,12 @@
 """
 Tweepy Twitter API library
 """
-__version__ = '3.1'
+__version__ = '3.3.0'
 __author__ = 'Joshua Roesslein'
 __license__ = 'MIT'
 
 from tweepy.models import Status, User, DirectMessage, Friendship, SavedSearch, SearchResults, ModelFactory, Category
-from tweepy.error import TweepError
+from tweepy.error import TweepError, RateLimitError
 from tweepy.api import API
 from tweepy.cache import Cache, MemoryCache, FileCache
 from tweepy.auth import OAuthHandler, AppAuthHandler
@@ -21,7 +21,5 @@ from tweepy.cursor import Cursor
 api = API()
 
 def debug(enable=True, level=1):
-
-    import httplib
-    httplib.HTTPConnection.debuglevel = level
-
+    from six.moves.http_client import HTTPConnection
+    HTTPConnection.debuglevel = level
diff --git a/tweepy/api.py b/tweepy/api.py
index 5310872..45c35ee 100644
--- a/tweepy/api.py
+++ b/tweepy/api.py
@@ -20,18 +20,21 @@ class API(object):
 
     def __init__(self, auth_handler=None,
                  host='api.twitter.com', search_host='search.twitter.com',
-                 cache=None, api_root='/1.1', search_root='',
-                 retry_count=0, retry_delay=0, retry_errors=None, timeout=60,
-                 parser=None, compression=False, wait_on_rate_limit=False,
+                 upload_host='upload.twitter.com', cache=None, api_root='/1.1',
+                 search_root='', upload_root='/1.1', retry_count=0,
+                 retry_delay=0, retry_errors=None, timeout=60, parser=None,
+                 compression=False, wait_on_rate_limit=False,
                  wait_on_rate_limit_notify=False, proxy=''):
         """ Api instance Constructor
 
         :param auth_handler:
         :param host:  url of the server of the rest api, default:'api.twitter.com'
         :param search_host: url of the search server, default:'search.twitter.com'
+        :param upload_host: url of the upload server, default:'upload.twitter.com'
         :param cache: Cache to query if a GET method is used, default:None
         :param api_root: suffix of the api version, default:'/1.1'
         :param search_root: suffix of the search version, default:''
+        :param upload_root: suffix of the upload version, default:'/1.1'
         :param retry_count: number of allowed retries, default:0
         :param retry_delay: delay in second between retries, default:0
         :param retry_errors: default:None
@@ -47,8 +50,10 @@ class API(object):
         self.auth = auth_handler
         self.host = host
         self.search_host = search_host
+        self.upload_host = upload_host
         self.api_root = api_root
         self.search_root = search_root
+        self.upload_root = upload_root
         self.cache = cache
         self.compression = compression
         self.retry_count = retry_count
@@ -170,11 +175,14 @@ class API(object):
             allowed_param=['id']
         )
 
-    @property
-    def update_status(self):
+    def update_status(self, media_ids=None, *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'
+            :allowed_param:'status', 'in_reply_to_status_id', 'lat', 'long', 'source', 'place_id', 'display_coordinates', 'media_ids'
         """
+        post_data = {}
+        if media_ids is not None:
+            post_data["media_ids"] = list_to_csv(media_ids)
+
         return bind_api(
             api=self,
             path='/statuses/update.json',
@@ -182,7 +190,25 @@ class API(object):
             payload_type='status',
             allowed_param=['status', 'in_reply_to_status_id', 'lat', 'long', 'source', 'place_id', 'display_coordinates'],
             require_auth=True
-        )
+        )(post_data=post_data, *args, **kwargs)
+
+    def media_upload(self, filename, *args, **kwargs):
+        """ :reference: https://dev.twitter.com/rest/reference/post/media/upload
+            :allowed_param:
+        """
+        f = kwargs.pop('file', None)
+        headers, post_data = API._pack_image(filename, 3072, form_field='media', f=f)
+        kwargs.update({'headers': headers, 'post_data': post_data})
+
+        return bind_api(
+            api=self,
+            path='/media/upload.json',
+            method='POST',
+            payload_type='media',
+            allowed_param=[],
+            require_auth=True,
+            upload_api=True
+        )(*args, **kwargs)
 
     def update_with_media(self, filename, *args, **kwargs):
         """ :reference: https://dev.twitter.com/rest/reference/post/statuses/update_with_media
@@ -502,13 +528,13 @@ class API(object):
     @property
     def friends(self):
         """ :reference: https://dev.twitter.com/rest/reference/get/friends/list
-            :allowed_param:'id', 'user_id', 'screen_name', 'cursor'
+            :allowed_param:'id', 'user_id', 'screen_name', 'cursor', 'skip_status', 'include_user_entities'
         """
         return bind_api(
             api=self,
             path='/friends/list.json',
             payload_type='user', payload_list=True,
-            allowed_param=['id', 'user_id', 'screen_name', 'cursor']
+            allowed_param=['id', 'user_id', 'screen_name', 'cursor', 'skip_status', 'include_user_entities']
         )
 
     @property
@@ -560,9 +586,39 @@ class API(object):
                            'skip_status', 'include_user_entities']
         )
 
+    @property
+    def get_settings(self):
+        """ :reference: https://dev.twitter.com/rest/reference/get/account/settings
+        """
+        return bind_api(
+            api=self,
+            path='/account/settings.json',
+            payload_type='json',
+            use_cache=False
+        )
+
+    @property
+    def set_settings(self):
+        """ :reference: https://dev.twitter.com/rest/reference/post/account/settings
+            :allowed_param:'sleep_time_enabled', 'start_sleep_time',
+            'end_sleep_time', 'time_zone', 'trend_location_woeid',
+            'allow_contributor_request', 'lang'
+        """
+        return bind_api(
+            api=self,
+            path='/account/settings.json',
+            method='POST',
+            payload_type='json',
+            allowed_param=['sleep_time_enabled', 'start_sleep_time',
+                           'end_sleep_time', 'time_zone',
+                           'trend_location_woeid', 'allow_contributor_request',
+                           'lang'],
+            use_cache=False
+        )
+
     def verify_credentials(self, **kargs):
         """ :reference: https://dev.twitter.com/rest/reference/get/account/verify_credentials
-            :allowed_param:'include_entities', 'skip_status'
+            :allowed_param:'include_entities', 'skip_status', 'include_email'
         """
         try:
             return bind_api(
@@ -570,7 +626,7 @@ class API(object):
                 path='/account/verify_credentials.json',
                 payload_type='user',
                 require_auth=True,
-                allowed_param=['include_entities', 'skip_status'],
+                allowed_param=['include_entities', 'skip_status', 'include_email'],
             )(**kargs)
         except TweepError as e:
             if e.response and e.response.status == 401:
@@ -986,7 +1042,7 @@ class API(object):
     @property
     def _add_list_members(self):
         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all
-            :allowed_param:'screen_name', 'user_id', 'slug', 'lit_id',
+            :allowed_param:'screen_name', 'user_id', 'slug', 'list_id',
             'owner_id', 'owner_screen_name'
 
         """
@@ -995,7 +1051,7 @@ class API(object):
             path='/lists/members/create_all.json',
             method='POST',
             payload_type='list',
-            allowed_param=['screen_name', 'user_id', 'slug', 'lit_id',
+            allowed_param=['screen_name', 'user_id', 'slug', 'list_id',
                            'owner_id', 'owner_screen_name'],
             require_auth=True
         )
@@ -1011,7 +1067,7 @@ class API(object):
     @property
     def _remove_list_members(self):
         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all
-            :allowed_param:'screen_name', 'user_id', 'slug', 'lit_id',
+            :allowed_param:'screen_name', 'user_id', 'slug', 'list_id',
             'owner_id', 'owner_screen_name'
 
         """
@@ -1020,7 +1076,7 @@ class API(object):
             path='/lists/members/destroy_all.json',
             method='POST',
             payload_type='list',
-            allowed_param=['screen_name', 'user_id', 'slug', 'lit_id',
+            allowed_param=['screen_name', 'user_id', 'slug', 'list_id',
                            'owner_id', 'owner_screen_name'],
             require_auth=True
         )
@@ -1148,7 +1204,7 @@ class API(object):
 
     @property
     def search(self):
-        """ :reference: https://dev.twitter.com/docs/api/1.1/get/search
+        """ :reference: https://dev.twitter.com/rest/reference/get/search/tweets
             :allowed_param:'q', 'lang', 'locale', 'since_id', 'geocode',
              'max_id', 'since', 'until', 'result_type', 'count',
               'include_entities', 'from', 'to', 'source']
@@ -1164,30 +1220,6 @@ class API(object):
         )
 
     @property
-    def trends_daily(self):
-        """ :reference: https://dev.twitter.com/docs/api/1.1/get/trends/daily
-            :allowed_param:'date', 'exclude'
-        """
-        return bind_api(
-            api=self,
-            path='/trends/daily.json',
-            payload_type='json',
-            allowed_param=['date', 'exclude']
-        )
-
-    @property
-    def trends_weekly(self):
-        """ :reference: https://dev.twitter.com/docs/api/1.1/get/trends/weekly
-            :allowed_param:'date', 'exclude'
-        """
-        return bind_api(
-            api=self,
-            path='/trends/weekly.json',
-            payload_type='json',
-            allowed_param=['date', 'exclude']
-        )
-
-    @property
     def reverse_geocode(self):
         """ :reference: https://dev.twitter.com/rest/reference/get/geo/reverse_geocode
             :allowed_param:'lat', 'long', 'accuracy', 'granularity', 'max_results'
@@ -1268,16 +1300,16 @@ class API(object):
         if f is None:
             try:
                 if os.path.getsize(filename) > (max_size * 1024):
-                    raise TweepError('File is too big, must be less than 700kb.')
-            except os.error:
-                raise TweepError('Unable to access file')
+                    raise TweepError('File is too big, must be less than %skb.' % max_size)
+            except os.error as e:
+                raise TweepError('Unable to access file: %s' % e.strerror)
 
             # build the mulitpart-formdata body
             fp = open(filename, 'rb')
         else:
             f.seek(0, 2)  # Seek to end of file
             if f.tell() > (max_size * 1024):
-                raise TweepError('File is too big, must be less than 700kb.')
+                raise TweepError('File is too big, must be less than %skb.' % max_size)
             f.seek(0)  # Reset to beginning of file
             fp = f
 
diff --git a/tweepy/binder.py b/tweepy/binder.py
index 12011a8..ba55570 100644
--- a/tweepy/binder.py
+++ b/tweepy/binder.py
@@ -12,7 +12,7 @@ import requests
 
 import logging
 
-from tweepy.error import TweepError
+from tweepy.error import TweepError, RateLimitError, is_rate_limit_error_message
 from tweepy.utils import convert_to_utf8_str
 from tweepy.models import Model
 
@@ -33,6 +33,7 @@ def bind_api(**config):
         method = config.get('method', 'GET')
         require_auth = config.get('require_auth', False)
         search_api = config.get('search_api', False)
+        upload_api = config.get('upload_api', False)
         use_cache = config.get('use_cache', True)
         session = requests.Session()
 
@@ -61,6 +62,8 @@ def bind_api(**config):
             # Pick correct URL root to use
             if self.search_api:
                 self.api_root = api.search_root
+            elif self.upload_api:
+                self.api_root = api.upload_root
             else:
                 self.api_root = api.api_root
 
@@ -69,6 +72,8 @@ def bind_api(**config):
 
             if self.search_api:
                 self.host = api.search_host
+            elif self.upload_api:
+                self.host = api.upload_host
             else:
                 self.host = api.host
 
@@ -181,7 +186,6 @@ def bind_api(**config):
                                                 auth=auth,
                                                 proxies=self.api.proxy)
                 except Exception as e:
-
                     raise TweepError('Failed to send request: %s' % e)
                 rem_calls = resp.headers.get('x-rate-limit-remaining')
                 if rem_calls is not None:
@@ -216,7 +220,11 @@ def bind_api(**config):
                     error_msg = self.parser.parse_error(resp.text)
                 except Exception:
                     error_msg = "Twitter error response: status code = %s" % resp.status_code
-                raise TweepError(error_msg, resp)
+
+                if is_rate_limit_error_message(error_msg):
+                    raise RateLimitError(error_msg, resp)
+                else:
+                    raise TweepError(error_msg, resp)
 
             # Parse the response payload
             result = self.parser.parse(self, resp.text)
diff --git a/tweepy/error.py b/tweepy/error.py
index 1c47a5a..7827029 100644
--- a/tweepy/error.py
+++ b/tweepy/error.py
@@ -6,7 +6,6 @@ from __future__ import print_function
 
 import six
 
-
 class TweepError(Exception):
     """Tweepy exception"""
 
@@ -17,3 +16,16 @@ class TweepError(Exception):
 
     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) \
+        and len(message) > 0 \
+        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
+    # as TweepError for backwards compatibility reasons.
+    pass
diff --git a/tweepy/models.py b/tweepy/models.py
index b8a3c15..30456b2 100644
--- a/tweepy/models.py
+++ b/tweepy/models.py
@@ -458,6 +458,16 @@ class Place(Model):
         return results
 
 
+class Media(Model):
+
+    @classmethod
+    def parse(cls, api, json):
+        media = cls(api)
+        for k, v in json.items():
+            setattr(media, k, v)
+        return media
+
+
 class ModelFactory(object):
     """
     Used by parsers for creating instances
@@ -475,6 +485,7 @@ class ModelFactory(object):
     list = List
     relation = Relation
     relationship = Relationship
+    media = Media
 
     json = JSONModel
     ids = IDModel
diff --git a/tweepy/streaming.py b/tweepy/streaming.py
index 0265c68..c637264 100644
--- a/tweepy/streaming.py
+++ b/tweepy/streaming.py
@@ -20,7 +20,7 @@ from tweepy.models import Status
 from tweepy.api import API
 from tweepy.error import TweepError
 
-from tweepy.utils import import_simplejson, urlencode_noplus
+from tweepy.utils import import_simplejson
 json = import_simplejson()
 
 STREAM_VERSION = '1.1'
@@ -79,6 +79,10 @@ class StreamListener(object):
         else:
             logging.error("Unknown message type: " + str(raw_data))
 
+    def keep_alive(self):
+        """Called when a keep-alive arrived"""
+        return
+
     def on_status(self, status):
         """Called when a new status arrives"""
         return
@@ -125,7 +129,7 @@ class StreamListener(object):
         https://dev.twitter.com/docs/streaming-apis/messages#Disconnect_messages_disconnect
         """
         return
-    
+
     def on_warning(self, notice):
         """Called when a disconnection warning message arrives"""
         return
@@ -146,25 +150,25 @@ class ReadBuffer(object):
 
     def __init__(self, stream, chunk_size):
         self._stream = stream
-        self._buffer = u""
+        self._buffer = ''
         self._chunk_size = chunk_size
 
     def read_len(self, length):
-        while True:
+        while not self._stream.closed:
             if len(self._buffer) >= length:
                 return self._pop(length)
             read_len = max(self._chunk_size, length - len(self._buffer))
-            self._buffer += self._stream.read(read_len).decode("ascii")
+            self._buffer += self._stream.read(read_len)
 
     def read_line(self, sep='\n'):
         start = 0
-        while True:
+        while not self._stream.closed:
             loc = self._buffer.find(sep, start)
             if loc >= 0:
                 return self._pop(loc + len(sep))
             else:
                 start = len(self._buffer)
-            self._buffer += self._stream.read(self._chunk_size).decode("ascii")
+            self._buffer += self._stream.read(self._chunk_size)
 
     def _pop(self, length):
         r = self._buffer[:length]
@@ -200,13 +204,17 @@ class Stream(object):
         self.verify = options.get("verify", True)
 
         self.api = API()
-        self.session = requests.Session()
-        self.session.headers = options.get("headers") or {}
-        self.session.params = None
+        self.headers = options.get("headers") or {}
+        self.new_session()
         self.body = None
         self.retry_time = self.retry_time_start
         self.snooze_time = self.snooze_time_step
 
+    def new_session(self):
+        self.session = requests.Session()
+        self.session.headers = self.headers
+        self.session.params = None
+
     def _run(self):
         # Authenticate
         url = "https://%s%s" % (self.host, self.url)
@@ -270,7 +278,7 @@ class Stream(object):
         if resp:
             resp.close()
 
-        self.session = requests.Session()
+        self.new_session()
 
         if exception:
             # call a handler first so that the exception can be logged.
@@ -284,12 +292,12 @@ class Stream(object):
     def _read_loop(self, resp):
         buf = ReadBuffer(resp.raw, self.chunk_size)
 
-        while self.running:
+        while self.running and not resp.raw.closed:
             length = 0
... 94 lines suppressed ...

-- 
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