From 37fc3e1aaac55bc7c807609bf1c2c32810c48839 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Thu, 5 Sep 2013 20:45:53 -0500 Subject: [PATCH 001/165] Coerce elements to strings as fallback --- twython/helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/twython/helpers.py b/twython/helpers.py index 24ad835d..a4826105 100644 --- a/twython/helpers.py +++ b/twython/helpers.py @@ -25,7 +25,10 @@ def _transparent_params(_params): elif isinstance(v, basestring) or isinstance(v, numeric_types): params[k] = v elif isinstance(v, list): - params[k] = ','.join(v) + try: + params[k] = ','.join(v) + except TypeError: + params[k] = ','.join(map(str,v)) else: continue # pragma: no cover return params, files From 21ce7edc2b57c027ac742683f044f698ebb2fe1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20M=C3=B6llerstrand?= Date: Fri, 6 Sep 2013 13:16:47 +0300 Subject: [PATCH 002/165] fix typo of APP_SECRET --- docs/usage/starting_out.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/starting_out.rst b/docs/usage/starting_out.rst index 9678fcff..b96e4523 100644 --- a/docs/usage/starting_out.rst +++ b/docs/usage/starting_out.rst @@ -122,7 +122,7 @@ Obtain an OAuth 2 Access Token .. code-block:: python APP_KEY = 'YOUR_APP_KEY' - APP_SECET = 'YOUR_APP_SECRET' + APP_SECRET = 'YOUR_APP_SECRET' twitter = Twython(APP_KEY, APP_SECRET, oauth_version=2) ACCESS_TOKEN = twitter.obtain_access_token() From a0b7c884022b80bc07f68ab31ec47017a2e956a8 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Tue, 24 Sep 2013 15:55:50 -0400 Subject: [PATCH 003/165] Fixing travis encrypted keys, version number and requests requirement --- .travis.yml | 28 ++++++---------------------- requirements.txt | 2 +- setup.py | 4 ++-- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index eb88c4d9..692131bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,28 +3,12 @@ python: - 2.6 - 2.7 - 3.3 -env: - global: - - secure: |- - ICEoq4tQdOCGzpQAgLHunOflhLqs7hb1xYuD6mXZCbO/L+hDI4+pImA9mZZd - doyfyFMuNprWfgdUdcAL3axliRR2TyQIt51FLf7bD01jJEPa0GOKlKE/s/+3 - 7oDs+kkSr81w9rvIqpx55nE8+DYzkh+TwDp5oAT+m/EE+lFItfk= - - secure: |- - LNowlSsXwgbFT0g8B4fDlBmOhycAfSxUNFiYBDn70H93WDLr9drEbE7Wl2Mv - AlGJw0kXey4K4oTV4+Bsy6gacsvCINpFk7f4ulTDHCH4MTONtG1n51RlkLW0 - SAH8di1DiXR3PqRv+NKixVsgI0ZCY1B78NuvaoDL68bfu0KEdHs= - - secure: |- - kC9hGpdJJesmZZGMXEoPWK/lzIU6vUeguV/yI2jLgRin0EKPsgds0qR4737x - 2Z2q1+CFUlvHkl+povGcm0/A1rkNqU0KKBcxRBu/XXRxJ3DWp7gIGsmoyWUW - 68kdPOwxywZ+tj6BCD7zmStKn4I3mSzTmGKaWj8ZT0wQ91tl0Y8= - - secure: |- - Y0M90wCpDWmSdBmgPCV2N9mMSaRMdEOis5r5sfUq/5aFTB/KDaSR9scM1g+L - 21OtvUBvaG1bdSzn0T+I5Fs/MkfbtTmuahogy83nsNDRpIZJmRIsHFmJw1fz - nEHD2Kbm4iLMYzrKto77KpxYSQMnc3sQKZjreaI31NLu+7raCAk= - - secure: |- - j1gePLSZF8SRcpF1AU+cBK5MSih5MrM1iGE6N7VWI0wrl+xh7wr3QLtVkAar - AeMFgwkz6QalfrKLsoUPFuNMv7vn+2CthC9pRv+NRk+4xV+37NysHFPR7JRo - xK2EC+DCiw2eJECnk9IPGQTgkVnFAQ3GLnsBSzzJ+UAkG2NjZ88= +env: + global: + - secure: USjLDneiXlVvEjkUVqTt+LBi0XJ4QhkRcJzqVXA9gEau1NTjAkNTPmHjUbOygp0dkfoV0uWrZKCw6fL1g+HJgWl0vHeHzcNl4mUkA+OwkGFHgaeIhvUfnyyJA8P3Zm21XHC+ehzMpEFN5fVNNhREjnRj+CXMc0FgA6knwBRobu4= + - secure: HFTL8UN1GkY6/GqygpgzWpYdWvhXRN8uTO//AgGzSg0FkHuDFcxRJigwCNI3PRImFkkRRVmUJDw18dolkx2h60w0wqD9K09DFpPTYfwmF9Bql+O3hjG9Ep9iu+CREwQTm2u66f36Q+pjaDiHWmr0kgi0zUTD7w0UvlN7gdFCMxk= + - secure: L3nD/BEOAqGTmd8Va9bQ8MZL5gbiNWuy9TvIxCmQ5bfdlAAdq371oqXpuy90JftaloapktjjmuSrsiszWgMMB/TGDO45h1LrYQEMEmA/4JN/uly3lUk8th9Rig+blKTG0q6X6GsX8UWA0xVeLtXzkddpNAOBgpeqb33pWmFkdcM= + - secure: OdVG7LVqQ13RQxxEUPEMHZb0seoZUNzq+oy/K1qe4ubcupqiMh47pxnDcei6vkVpMn8QIvbG9lcV0oOREqJ+m+g3wUA5JX95liFqLNsmLMMVgP3yjYDf7KoNHckA5H5BwIYO/AFCBdwyNN0h439kwSciCbIr70UModVkvdWoFLI= - SCREEN_NAME=__twython__ - PROTECTED_TWITTER_1=TwythonSecure1 - PROTECTED_TWITTER_2=TwythonSecure2 diff --git a/requirements.txt b/requirements.txt index b7897fbf..cab175e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ coverage==3.6.0 -requests==1.2.2 +requests==2.0.0 requests_oauthlib==0.3.2 python-coveralls==2.1.0 nose-cov==1.6 diff --git a/setup.py b/setup.py index e0501a95..710b0979 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import setup __author__ = 'Ryan McGrath ' -__version__ = '3.0.0' +__version__ = '3.1.0' packages = [ 'twython', @@ -20,7 +20,7 @@ setup( name='twython', version=__version__, - install_requires=['requests==1.2.3', 'requests_oauthlib==0.3.2'], + install_requires=['requests==2.0.0', 'requests_oauthlib==0.3.2'], author='Ryan McGrath', author_email='ryan@venodesigns.net', license=open('LICENSE').read(), From 85a327163de628cd721789d7d2c3ab6bc6a84998 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Tue, 24 Sep 2013 17:50:50 -0400 Subject: [PATCH 004/165] Special encrypt for access token, this probably won't work --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 692131bf..fc0db421 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ env: - secure: HFTL8UN1GkY6/GqygpgzWpYdWvhXRN8uTO//AgGzSg0FkHuDFcxRJigwCNI3PRImFkkRRVmUJDw18dolkx2h60w0wqD9K09DFpPTYfwmF9Bql+O3hjG9Ep9iu+CREwQTm2u66f36Q+pjaDiHWmr0kgi0zUTD7w0UvlN7gdFCMxk= - secure: L3nD/BEOAqGTmd8Va9bQ8MZL5gbiNWuy9TvIxCmQ5bfdlAAdq371oqXpuy90JftaloapktjjmuSrsiszWgMMB/TGDO45h1LrYQEMEmA/4JN/uly3lUk8th9Rig+blKTG0q6X6GsX8UWA0xVeLtXzkddpNAOBgpeqb33pWmFkdcM= - secure: OdVG7LVqQ13RQxxEUPEMHZb0seoZUNzq+oy/K1qe4ubcupqiMh47pxnDcei6vkVpMn8QIvbG9lcV0oOREqJ+m+g3wUA5JX95liFqLNsmLMMVgP3yjYDf7KoNHckA5H5BwIYO/AFCBdwyNN0h439kwSciCbIr70UModVkvdWoFLI= + - secure: RJgKNXCxQZxjkxqinv6x96hGZvfJpjOIaRpH9YUJrCZeqLbS1FUOvTWkze5nZQw4DL14zBmpGZXYC9twSYvscEGNnaEhxDKygiFfLosERRa65N57kphD0AsGNe9YAhWUGDbCxnZ0PYHc48DYxX+jPxqsQ/2bxZbXVoJg4pZFP3o= - SCREEN_NAME=__twython__ - PROTECTED_TWITTER_1=TwythonSecure1 - PROTECTED_TWITTER_2=TwythonSecure2 @@ -24,3 +25,9 @@ branches: - master after_success: - coveralls +before_script: + - |- + ACCESS_TOKEN_B64=U2FsdGVkX1+OsKZdDqnhWy1jwIUKKqjp2eeTeIS2S2Z9xfwzg2XPavn01lV9Ml7j + LULxl/l4021YZ3DdYpudm7vQHr+lJGMM1TNPdf0QIusxWYZp7pGayl6uourm8iEY + WIEpMtCNL3l6Ntluw077oVP9TW0kiGQ8JROemIHLTOY= + - ACCESS_TOKEN=`openssl enc -d -aes-256-cbc -a $ACCESS_TOKEN_B64 -pass env:$ACCESS_TOKEN_SECRET` From 58ff3de7a198b582725fe2760a271533e7426892 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Tue, 24 Sep 2013 17:58:59 -0400 Subject: [PATCH 005/165] Trying this travis var stuff again! --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index fc0db421..07b2744c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,10 @@ env: - TEST_TWEET_ID=332992304010899457 - TEST_LIST_SLUG=team - TEST_LIST_OWNER_SCREEN_NAME=twitterapi + - |- + ACCESS_TOKEN_B64=U2FsdGVkX1+OsKZdDqnhWy1jwIUKKqjp2eeTeIS2S2Z9xfwzg2XPavn01lV9Ml7j + LULxl/l4021YZ3DdYpudm7vQHr+lJGMM1TNPdf0QIusxWYZp7pGayl6uourm8iEY + WIEpMtCNL3l6Ntluw077oVP9TW0kiGQ8JROemIHLTOY= install: pip install -r requirements.txt script: nosetests -v -w tests/ --logging-filter="twython" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing notifications: @@ -26,8 +30,4 @@ branches: after_success: - coveralls before_script: - - |- - ACCESS_TOKEN_B64=U2FsdGVkX1+OsKZdDqnhWy1jwIUKKqjp2eeTeIS2S2Z9xfwzg2XPavn01lV9Ml7j - LULxl/l4021YZ3DdYpudm7vQHr+lJGMM1TNPdf0QIusxWYZp7pGayl6uourm8iEY - WIEpMtCNL3l6Ntluw077oVP9TW0kiGQ8JROemIHLTOY= - - ACCESS_TOKEN=`openssl enc -d -aes-256-cbc -a $ACCESS_TOKEN_B64 -pass env:$ACCESS_TOKEN_SECRET` + - export ACCESS_TOKEN=$(echo $ACCESS_TOKEN_B64 | openssl enc -d -aes-256-cbc -a -pass env:ACCESS_TOKEN_SECRET) From bc373c3436d8407d9ce821be5e2da4fe1f476521 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Tue, 24 Sep 2013 18:12:17 -0400 Subject: [PATCH 006/165] Gotsta see what the access token is --- tests/test_core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_core.py b/tests/test_core.py index 710ba1b4..3a38ec83 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -29,6 +29,7 @@ def setUp(self): oauth_token, oauth_token_secret, client_args=client_args) + print 'access token is', access_token self.oauth2_api = Twython(app_key, access_token=access_token, client_args=oauth2_client_args) From 082528490eb1674c2b0737b8d832d171bdd6d723 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 25 Sep 2013 10:45:24 -0400 Subject: [PATCH 007/165] Update docs [ci skip] --- docs/usage/advanced_usage.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index 74246f3c..db363019 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -102,8 +102,8 @@ Here is an example of sending the request through proxies: client_args = { 'proxies': { - 'http': '10.0.10.1:8000', - 'https': '10.0.10.1:8001', + 'http': 'http://10.0.10.1:8000', + 'https': 'https://10.0.10.1:8001', } } @@ -122,8 +122,8 @@ or both (and set a timeout variable): 'User-Agent': 'My App Name' }, 'proxies': { - 'http': '10.0.10.1:8000', - 'https': '10.0.10.1:8001', + 'http': 'http://10.0.10.1:8000', + 'https': 'https://10.0.10.1:8001', } 'timeout': 300, } From 21257d0475a15625d0a2a2c3bcb899428fd0bfd2 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 25 Sep 2013 10:59:22 -0400 Subject: [PATCH 008/165] One line b64 string for ACCESS_TOKEN --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 07b2744c..a4ebb21b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,10 +16,7 @@ env: - TEST_TWEET_ID=332992304010899457 - TEST_LIST_SLUG=team - TEST_LIST_OWNER_SCREEN_NAME=twitterapi - - |- - ACCESS_TOKEN_B64=U2FsdGVkX1+OsKZdDqnhWy1jwIUKKqjp2eeTeIS2S2Z9xfwzg2XPavn01lV9Ml7j - LULxl/l4021YZ3DdYpudm7vQHr+lJGMM1TNPdf0QIusxWYZp7pGayl6uourm8iEY - WIEpMtCNL3l6Ntluw077oVP9TW0kiGQ8JROemIHLTOY= + - ACCESS_TOKEN_B64=U2FsdGVkX18QdBhvMNshM4PGy04tU3HLwKP+nNSoNZHKsvGLjELcWEXN2LIu/T+yngX1vGONf9lo14ElnfS4k7sfhiru8phR4+rZuBVP3bDvC2A6fXJuhuLqNhBrWqg32WQewvxLWDWBoKmnvRHg5b74GHh+IN/12tU0cBF2HK8= install: pip install -r requirements.txt script: nosetests -v -w tests/ --logging-filter="twython" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing notifications: From 636e52a8c53e96a047b906cc38b2232a9d94ad86 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 25 Sep 2013 11:14:27 -0400 Subject: [PATCH 009/165] Fixing decrypt, remove print --- .travis.yml | 2 +- tests/test_core.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a4ebb21b..9e4e7fc0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,4 +27,4 @@ branches: after_success: - coveralls before_script: - - export ACCESS_TOKEN=$(echo $ACCESS_TOKEN_B64 | openssl enc -d -aes-256-cbc -a -pass env:ACCESS_TOKEN_SECRET) + - export ACCESS_TOKEN=$(echo $ACCESS_TOKEN_B64 | openssl enc -d -aes-256-cbc -A -a -pass env:ACCESS_TOKEN_SECRET) diff --git a/tests/test_core.py b/tests/test_core.py index 3a38ec83..710ba1b4 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -29,7 +29,6 @@ def setUp(self): oauth_token, oauth_token_secret, client_args=client_args) - print 'access token is', access_token self.oauth2_api = Twython(app_key, access_token=access_token, client_args=oauth2_client_args) From a6399778734d9fea1528a21d4609a0a7500870b8 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 25 Sep 2013 11:24:13 -0400 Subject: [PATCH 010/165] Should have left the print in --- tests/test_core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_core.py b/tests/test_core.py index 710ba1b4..3a38ec83 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -29,6 +29,7 @@ def setUp(self): oauth_token, oauth_token_secret, client_args=client_args) + print 'access token is', access_token self.oauth2_api = Twython(app_key, access_token=access_token, client_args=oauth2_client_args) From 6da8805545fdfb40a62d6fd04aeb78400731bdda Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 25 Sep 2013 11:59:55 -0400 Subject: [PATCH 011/165] =?UTF-8?q?Fix=20decrypt,=20remove=20print?= =?UTF-8?q?=E2=80=A6.=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .travis.yml | 2 +- tests/test_core.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9e4e7fc0..8411c027 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,4 +27,4 @@ branches: after_success: - coveralls before_script: - - export ACCESS_TOKEN=$(echo $ACCESS_TOKEN_B64 | openssl enc -d -aes-256-cbc -A -a -pass env:ACCESS_TOKEN_SECRET) + - export ACCESS_TOKEN=$(echo $ACCESS_TOKEN_B64 | openssl enc -d -aes-256-cbc -A -a -pass env:ACCESS_TOKEN_PASS) diff --git a/tests/test_core.py b/tests/test_core.py index 3a38ec83..710ba1b4 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -29,7 +29,6 @@ def setUp(self): oauth_token, oauth_token_secret, client_args=client_args) - print 'access token is', access_token self.oauth2_api = Twython(app_key, access_token=access_token, client_args=oauth2_client_args) From f6d458e7587069852af1cb05aba4cfc5e382df4b Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 25 Sep 2013 12:58:40 -0400 Subject: [PATCH 012/165] Fixes #230 --- twython/streaming/api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/twython/streaming/api.py b/twython/streaming/api.py index ef8924aa..a27e6cd9 100644 --- a/twython/streaming/api.py +++ b/twython/streaming/api.py @@ -21,7 +21,7 @@ class TwythonStreamer(object): def __init__(self, app_key, app_secret, oauth_token, oauth_token_secret, timeout=300, retry_count=None, retry_in=10, client_args=None, - handlers=None): + handlers=None, chunk_size=1): """Streaming class for a friendly streaming user experience Authentication IS required to use the Twitter Streaming API @@ -42,6 +42,9 @@ def __init__(self, app_key, app_secret, oauth_token, oauth_token_secret, [ex. headers, proxies, verify(SSL verification)] :param handlers: (optional) Array of message types for which corresponding handlers will be called + + :param chunk_size: (optional) Define the buffer size before data is + actually returned from the Streaming API. Default: 1 """ self.auth = OAuth1(app_key, app_secret, @@ -124,7 +127,7 @@ def _send(retry_counter): while self.connected: response = _send(retry_counter) - for line in response.iter_lines(): + for line in response.iter_lines(self.chunk_size): if not self.connected: break if line: From 99067c3e6ff6c0738d514bdfff9eea2c939cd807 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 25 Sep 2013 13:02:52 -0400 Subject: [PATCH 013/165] Fixes #254 and forgot to def chunk_size --- twython/streaming/api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/twython/streaming/api.py b/twython/streaming/api.py index a27e6cd9..6fa247ef 100644 --- a/twython/streaming/api.py +++ b/twython/streaming/api.py @@ -10,6 +10,7 @@ from .. import __version__ from ..compat import json, is_py3 +from ..helpers import _transparent_params from .types import TwythonStreamerTypes import requests @@ -88,6 +89,8 @@ def __init__(self, app_key, app_secret, oauth_token, oauth_token_secret, self.handlers = handlers if handlers else ['delete', 'limit', 'disconnect'] + self.chunk_size = chunk_size + def _request(self, url, method='GET', params=None): """Internal stream request handling""" self.connected = True @@ -95,6 +98,7 @@ def _request(self, url, method='GET', params=None): method = method.lower() func = getattr(self.client, method) + params, _ = _transparent_params(params) def _send(retry_counter): requests_args = {} From 28b6d68b3b12ab0410d4201c269f900e0e44e298 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 25 Sep 2013 16:21:49 -0400 Subject: [PATCH 014/165] Update changelog, fix version number [ci skip] --- HISTORY.rst | 4 +++- twython/__init__.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 3c35a770..2fb47346 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,7 +3,7 @@ History ------- -3.1.0 (2013-xx-xx) +3.1.0 (2013-09-25) ++++++++++++++++++ - Added ``html_for_tweet`` static method. This method accepts a tweet object returned from a Twitter API call and will return a string with urls, mentions and hashtags in the tweet replaced with HTML. @@ -14,6 +14,8 @@ History - ``Twython.search_gen`` has been deprecated. Please use ``twitter.cursor(twitter.search, q='your_query')`` instead, where ``twitter`` is your ``Twython`` instance. - Added methods ``get_list_memberships``, ``get_twitter_configuration``, ``get_supported_languages``, ``get_privacy_policy``, ``get_tos`` - Added ``auth_endpoint`` parameter to ``Twython.__init__`` for cases when the right parameters weren't being shown during the authentication step. +- Fixed streaming issue where results wouldn't be returned for streams that weren't so active (See https://github.com/ryanmcgrath/twython/issues/202#issuecomment-19915708) +- Streaming API now uses ``_transparent_params`` so when passed ``True`` or ``False`` or an array, etc. Twython formats it to meet Twitter parameter standards (i.e. ['ryanmcgrath', 'mikehelmick', 'twitterapi'] would convert to string 'ryanmcgrath,mikehelmick,twitterapi') 3.0.0 (2013-06-18) ++++++++++++++++++ diff --git a/twython/__init__.py b/twython/__init__.py index 52f48368..4a90afcf 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -18,7 +18,7 @@ """ __author__ = 'Ryan McGrath ' -__version__ = '3.0.0' +__version__ = '3.1.0' from .api import Twython from .streaming import TwythonStreamer From 9bf83fd933b61fbdc025636621ba1ed7c505d260 Mon Sep 17 00:00:00 2001 From: Remi Rampin Date: Wed, 9 Oct 2013 14:39:45 -0400 Subject: [PATCH 015/165] Don't mask TwythonStreamer exceptions Exceptions in handlers or on_success which subclass ValueError would previously be caught and reported as a JSON decoding problem, and on_error() would be called (with status_code=200). --- twython/streaming/api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/twython/streaming/api.py b/twython/streaming/api.py index 6fa247ef..c814acbf 100644 --- a/twython/streaming/api.py +++ b/twython/streaming/api.py @@ -139,14 +139,15 @@ def _send(retry_counter): if is_py3: line = line.decode('utf-8') data = json.loads(line) + except ValueError: # pragma: no cover + self.on_error(response.status_code, 'Unable to decode response, not valid JSON.') + else: if self.on_success(data): # pragma: no cover for message_type in self.handlers: if message_type in data: handler = getattr(self, 'on_' + message_type, None) if handler and callable(handler) and not handler(data.get(message_type)): break - except ValueError: # pragma: no cover - self.on_error(response.status_code, 'Unable to decode response, not valid JSON.') response.close() From 6f77addef86d4ab414bea1251913e44b946a1322 Mon Sep 17 00:00:00 2001 From: Chris Seymour Date: Thu, 10 Oct 2013 15:53:02 +0100 Subject: [PATCH 016/165] [Trivial] Added missing comma. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 4727dc55..143a2776 100644 --- a/README.rst +++ b/README.rst @@ -156,7 +156,7 @@ Create a Twython instance with your application keys and the users OAuth tokens .. code-block:: python from twython import Twython - twitter = Twython(APP_KEY, APP_SECRET + twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) Authenticated Users Home Timeline From 5ff6db186e46e7331789eb10e859aba18adef2ac Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Sun, 20 Oct 2013 14:51:39 +1100 Subject: [PATCH 017/165] Update setup.py Copied setup import from Python-Requests so it does not fail on some platforms (e.g. Nokia N9/MeeGo). --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 710b0979..85c1e646 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,10 @@ import os import sys -from setuptools import setup +try: + from setuptools import setup +except ImportError: + from distutils.core import setup __author__ = 'Ryan McGrath ' __version__ = '3.1.0' From 36ef365d43ccb298829a6c8b74fe88c5e1901bfd Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 23 Oct 2013 00:10:53 -0400 Subject: [PATCH 018/165] Delete shorten_url.py Fixes #277 [ci skip] --- examples/shorten_url.py | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 examples/shorten_url.py diff --git a/examples/shorten_url.py b/examples/shorten_url.py deleted file mode 100644 index 61eb1058..00000000 --- a/examples/shorten_url.py +++ /dev/null @@ -1,6 +0,0 @@ -from twython import Twython - -# Shortening URLs requires no authentication, huzzah -shortURL = Twython.shorten_url('http://www.webs.com/') - -print shortURL From 6c2cac76a56d61699af190d95bd22a88493199eb Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Sat, 26 Oct 2013 19:04:08 -0400 Subject: [PATCH 019/165] Update lib versions, update gitignore [ci skip] --- .gitignore | 2 ++ requirements.txt | 4 ++-- setup.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 7dadcf7f..60d1cc63 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ nosetests.xml docs/_build test.py + +.venv diff --git a/requirements.txt b/requirements.txt index cab175e6..a103b0e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ coverage==3.6.0 -requests==2.0.0 -requests_oauthlib==0.3.2 +requests==2.0.1 +requests_oauthlib==0.4.0 python-coveralls==2.1.0 nose-cov==1.6 diff --git a/setup.py b/setup.py index 85c1e646..a7ff8df6 100755 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup( name='twython', version=__version__, - install_requires=['requests==2.0.0', 'requests_oauthlib==0.3.2'], + install_requires=['requests==2.0.1', 'requests_oauthlib==0.4.0'], author='Ryan McGrath', author_email='ryan@venodesigns.net', license=open('LICENSE').read(), From 638f75b93d531993983a2f789c9af8579b6e63ed Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Sat, 26 Oct 2013 19:04:36 -0400 Subject: [PATCH 020/165] Fixes #266 --- tests/test_auth.py | 7 +++++++ twython/api.py | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/test_auth.py b/tests/test_auth.py index 12f54b2a..edefe07b 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -9,6 +9,8 @@ class TwythonAuthTestCase(unittest.TestCase): def setUp(self): self.api = Twython(app_key, app_secret) self.bad_api = Twython('BAD_APP_KEY', 'BAD_APP_SECRET') + self.bad_api_invalid_tokens = Twython('BAD_APP_KEY', 'BAD_APP_SECRET', + 'BAD_OT', 'BAD_OTS') self.oauth2_api = Twython(app_key, app_secret, oauth_version=2) self.oauth2_bad_api = Twython('BAD_APP_KEY', 'BAD_APP_SECRET', @@ -31,6 +33,11 @@ def test_get_authorized_tokens_bad_tokens(self): self.assertRaises(TwythonError, self.bad_api.get_authorized_tokens, 'BAD_OAUTH_VERIFIER') + def test_get_authorized_tokens_invalid_or_expired_tokens(self): + """Test getting final token fails when invalid or expired tokens have been passed""" + self.assertRaises(TwythonError, self.bad_api_invalid_tokens.get_authorized_tokens, + 'BAD_OAUTH_VERIFIER') + def test_get_authentication_tokens_raises_error_when_oauth2(self): """Test when API is set for OAuth 2, get_authentication_tokens raises a TwythonError""" diff --git a/twython/api.py b/twython/api.py index 19efb965..39b8f287 100644 --- a/twython/api.py +++ b/twython/api.py @@ -307,7 +307,21 @@ def get_authorized_tokens(self, oauth_verifier): if self.oauth_version != 1: raise TwythonError('This method can only be called when your OAuth version is 1.0.') - response = self.client.get(self.access_token_url, params={'oauth_verifier': oauth_verifier}) + response = self.client.get(self.access_token_url, params={'oauth_verifier': oauth_verifier}, headers={'Content-Type': 'application/json'}) + + if response.status_code == 401: + try: + try: + # try to get json + content = response.json() + except AttributeError: # pragma: no cover + # if unicode detected + content = json.loads(response.content) + except ValueError: + content = {} + + raise TwythonError(content.get('error', 'Invalid / expired Token'), error_code=response.status_code) + authorized_tokens = dict(parse_qsl(response.content.decode('utf-8'))) if not authorized_tokens: raise TwythonError('Unable to decode authorized tokens.') From cf766311f0ea4030c8981894182ca49b425682a2 Mon Sep 17 00:00:00 2001 From: drevicko Date: Fri, 8 Nov 2013 12:23:22 +1100 Subject: [PATCH 021/165] added option to cursor() to yeild "pages" - ie: iterators of items for each page returned by twitter modified: api.py --- twython/api.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/twython/api.py b/twython/api.py index 39b8f287..6e9acacd 100644 --- a/twython/api.py +++ b/twython/api.py @@ -388,7 +388,7 @@ def search_gen(self, search_query, **params): # pragma: no cover ) return self.cursor(self.search, q=search_query, **params) - def cursor(self, function, **params): + def cursor(self, function, **params, returnPages = False): """Returns a generator for results that match a specified query. :param function: Instance of a Twython function (Twython.get_home_timeline, Twython.search) @@ -418,8 +418,11 @@ def cursor(self, function, **params): else: results = content - for result in results: - yield result + if returnPages: + yield results + else: + for result in results: + yield result if function.iter_mode == 'cursor' and content['next_cursor_str'] == '0': raise StopIteration @@ -438,8 +441,11 @@ def cursor(self, function, **params): except (TypeError, ValueError): # pragma: no cover raise TwythonError('Unable to generate next page of search results, `page` is not a number.') - for result in self.cursor(function, **params): - yield result + if returnPages: + yield self.cursor(function, **params) + else: + for result in self.cursor(function, **params): + yield result @staticmethod def unicode2utf8(text): From 252ded6e00cfb751a27a84bfd8b2c57d2575d69e Mon Sep 17 00:00:00 2001 From: drevicko Date: Fri, 8 Nov 2013 12:43:29 +1100 Subject: [PATCH 022/165] fixed recursive part for returning cursor() pages modified: api.py --- twython/api.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/twython/api.py b/twython/api.py index 6e9acacd..9fc673b7 100644 --- a/twython/api.py +++ b/twython/api.py @@ -388,7 +388,7 @@ def search_gen(self, search_query, **params): # pragma: no cover ) return self.cursor(self.search, q=search_query, **params) - def cursor(self, function, **params, returnPages = False): + def cursor(self, function, returnPages = False, **params): """Returns a generator for results that match a specified query. :param function: Instance of a Twython function (Twython.get_home_timeline, Twython.search) @@ -441,11 +441,8 @@ def cursor(self, function, **params, returnPages = False): except (TypeError, ValueError): # pragma: no cover raise TwythonError('Unable to generate next page of search results, `page` is not a number.') - if returnPages: - yield self.cursor(function, **params) - else: - for result in self.cursor(function, **params): - yield result + for result in self.cursor(function, returnPages = returnPages, **params): + yield result @staticmethod def unicode2utf8(text): From 32337fd036df907a21e9f5b1758abeb58649cb4e Mon Sep 17 00:00:00 2001 From: drevicko Date: Fri, 8 Nov 2013 13:03:23 +1100 Subject: [PATCH 023/165] changed cursor() from recursion to while loop modified: api.py --- twython/api.py | 52 ++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/twython/api.py b/twython/api.py index 39b8f287..2d5d065d 100644 --- a/twython/api.py +++ b/twython/api.py @@ -408,38 +408,36 @@ def cursor(self, function, **params): if not hasattr(function, 'iter_mode'): raise TwythonError('Unable to create generator for Twython method "%s"' % function.__name__) - content = function(**params) + while True: + content = function(**params) - if not content: - raise StopIteration + if not content: + raise StopIteration - if hasattr(function, 'iter_key'): - results = content.get(function.iter_key) - else: - results = content + if hasattr(function, 'iter_key'): + results = content.get(function.iter_key) + else: + results = content - for result in results: - yield result + for result in results: + yield result - if function.iter_mode == 'cursor' and content['next_cursor_str'] == '0': - raise StopIteration + if function.iter_mode == 'cursor' and content['next_cursor_str'] == '0': + raise StopIteration - try: - if function.iter_mode == 'id': - if not 'max_id' in params: - # Add 1 to the id because since_id and max_id are inclusive - if hasattr(function, 'iter_metadata'): - since_id = content[function.iter_metadata].get('since_id_str') - else: - since_id = content[0]['id_str'] - params['since_id'] = (int(since_id) - 1) - elif function.iter_mode == 'cursor': - params['cursor'] = content['next_cursor_str'] - except (TypeError, ValueError): # pragma: no cover - raise TwythonError('Unable to generate next page of search results, `page` is not a number.') - - for result in self.cursor(function, **params): - yield result + try: + if function.iter_mode == 'id': + if not 'max_id' in params: + # Add 1 to the id because since_id and max_id are inclusive + if hasattr(function, 'iter_metadata'): + since_id = content[function.iter_metadata].get('since_id_str') + else: + since_id = content[0]['id_str'] + params['since_id'] = (int(since_id) - 1) + elif function.iter_mode == 'cursor': + params['cursor'] = content['next_cursor_str'] + except (TypeError, ValueError): # pragma: no cover + raise TwythonError('Unable to generate next page of search results, `page` is not a number.') @staticmethod def unicode2utf8(text): From c2068466af154f6915160b6aa86b8326b18661f2 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Thu, 5 Dec 2013 18:15:30 -0500 Subject: [PATCH 024/165] Update requests version, HISTORY.rst, prepare for 3.1.1 release --- HISTORY.rst | 10 ++++++++++ requirements.txt | 2 +- setup.py | 4 ++-- twython/__init__.py | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 2fb47346..73a3cd76 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,16 @@ History ------- +3.1.1 (2013-12-05) +++++++++++++++++++ + +- Update `requests` version to 2.1.0. +- Fixed: Streaming issue where `Exceptions` in handlers or `on_success` which subclass `ValueError` would +previously be caught and reported as a JSON decoding problem, and +`on_error()` would be called (with status_code=200) +- Fixed issue where XML was returned when bad tokens were passed to `get_authorized_tokens` +- Fixed import for `setup` causing installation to fail on some devices (eg. Nokia N9/MeeGo) + 3.1.0 (2013-09-25) ++++++++++++++++++ diff --git a/requirements.txt b/requirements.txt index a103b0e8..91682748 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ coverage==3.6.0 -requests==2.0.1 +requests==2.1.0 requests_oauthlib==0.4.0 python-coveralls==2.1.0 nose-cov==1.6 diff --git a/setup.py b/setup.py index a7ff8df6..7c8df69c 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.1.0' +__version__ = '3.1.1' packages = [ 'twython', @@ -23,7 +23,7 @@ setup( name='twython', version=__version__, - install_requires=['requests==2.0.1', 'requests_oauthlib==0.4.0'], + install_requires=['requests==2.1.0', 'requests_oauthlib==0.4.0'], author='Ryan McGrath', author_email='ryan@venodesigns.net', license=open('LICENSE').read(), diff --git a/twython/__init__.py b/twython/__init__.py index 4a90afcf..bd4bc0b7 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -18,7 +18,7 @@ """ __author__ = 'Ryan McGrath ' -__version__ = '3.1.0' +__version__ = '3.1.1' from .api import Twython from .streaming import TwythonStreamer From 92479cd75baf20a3f57441d3e3b388d32fa27447 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Thu, 5 Dec 2013 18:22:59 -0500 Subject: [PATCH 025/165] Update conf.py [ci skip] --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 1f5311d2..6367fe26 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '3.1.0' +version = '3.1.1' # The full version, including alpha/beta/rc tags. -release = '3.1.0' +release = '3.1.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 74b2f97f2c5512361c910dee5673ed51b5fb675c Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Fri, 6 Dec 2013 11:12:29 -0500 Subject: [PATCH 026/165] Update HISTORY.rst [ci skip] --- HISTORY.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 73a3cd76..e56d6579 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -7,9 +7,7 @@ History ++++++++++++++++++ - Update `requests` version to 2.1.0. -- Fixed: Streaming issue where `Exceptions` in handlers or `on_success` which subclass `ValueError` would -previously be caught and reported as a JSON decoding problem, and -`on_error()` would be called (with status_code=200) +- Fixed: Streaming issue where `Exceptions` in handlers or `on_success` which subclass `ValueError` would previously be caught and reported as a JSON decoding problem, and `on_error()` would be called (with status_code=200) - Fixed issue where XML was returned when bad tokens were passed to `get_authorized_tokens` - Fixed import for `setup` causing installation to fail on some devices (eg. Nokia N9/MeeGo) From 0937fbe92950c17e2e7701312ea839b562e66415 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Fri, 6 Dec 2013 11:14:39 -0500 Subject: [PATCH 027/165] Fix Changelog, update version [ci skip] --- HISTORY.rst | 5 +++++ docs/conf.py | 4 ++-- setup.py | 2 +- twython/__init__.py | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index e56d6579..1259227a 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,11 @@ History ------- +3.1.2 (2013-12-05) +++++++++++++++++++ + +- Fixed Changelog (HISTORY.rst) + 3.1.1 (2013-12-05) ++++++++++++++++++ diff --git a/docs/conf.py b/docs/conf.py index 6367fe26..b0b98eb4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '3.1.1' +version = '3.1.2' # The full version, including alpha/beta/rc tags. -release = '3.1.1' +release = '3.1.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 7c8df69c..d2d3570d 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.1.1' +__version__ = '3.1.2' packages = [ 'twython', diff --git a/twython/__init__.py b/twython/__init__.py index bd4bc0b7..ab4df37f 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -18,7 +18,7 @@ """ __author__ = 'Ryan McGrath ' -__version__ = '3.1.1' +__version__ = '3.1.2' from .api import Twython from .streaming import TwythonStreamer From d5dfcb7e8626ba2149049040573ac151205f143b Mon Sep 17 00:00:00 2001 From: Natan L Date: Sun, 15 Dec 2013 20:28:43 -0800 Subject: [PATCH 028/165] Fix typo 'SECERT' --> 'SECRET' --- docs/usage/starting_out.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/starting_out.rst b/docs/usage/starting_out.rst index b96e4523..0a681acb 100644 --- a/docs/usage/starting_out.rst +++ b/docs/usage/starting_out.rst @@ -98,7 +98,7 @@ Once you have the final user tokens, store them in a database for later use! .. code-block:: python OAUTH_TOKEN = final_step['oauth_token'] - OAUTH_TOKEN_SECERT = final_step['oauth_token_secret'] + OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] .. _oauth2: From 25eca18da3435ca97212fc9f7e1b304911e26b54 Mon Sep 17 00:00:00 2001 From: Cash Costello Date: Sun, 5 Jan 2014 14:34:37 -0500 Subject: [PATCH 029/165] fixed typo in documentation of cursor() --- twython/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/api.py b/twython/api.py index 39b8f287..ef1aac63 100644 --- a/twython/api.py +++ b/twython/api.py @@ -392,7 +392,7 @@ def cursor(self, function, **params): """Returns a generator for results that match a specified query. :param function: Instance of a Twython function (Twython.get_home_timeline, Twython.search) - :param \*\*params: Extra parameters to send with your request (usually parameters excepted by the Twitter API endpoint) + :param \*\*params: Extra parameters to send with your request (usually parameters accepted by the Twitter API endpoint) :rtype: generator Usage:: From 9cbd3d6fbc23a878970e298e1675ae3b2eb525c0 Mon Sep 17 00:00:00 2001 From: cash Date: Thu, 9 Jan 2014 22:59:23 -0500 Subject: [PATCH 030/165] added some basic tests of the core request() method using dropbox/responses --- requirements.txt | 1 + tests/test_core.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/requirements.txt b/requirements.txt index 91682748..917c31ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ requests==2.1.0 requests_oauthlib==0.4.0 python-coveralls==2.1.0 nose-cov==1.6 +responses==0.2.0 diff --git a/tests/test_core.py b/tests/test_core.py index 710ba1b4..0d701f2f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -9,6 +9,7 @@ import time import unittest +import responses class TwythonAPITestCase(unittest.TestCase): @@ -32,6 +33,94 @@ def setUp(self): self.oauth2_api = Twython(app_key, access_token=access_token, client_args=oauth2_client_args) + @responses.activate + def test_request_should_handle_full_endpoint(self): + url = 'https://api.twitter.com/1.1/search/tweets.json' + responses.add(responses.GET, url) + + self.api.request(url) + + self.assertEqual(1, len(responses.calls)) + self.assertEqual(url, responses.calls[0].request.url) + + @responses.activate + def test_request_should_handle_relative_endpoint(self): + url = 'https://api.twitter.com/1.1/search/tweets.json' + responses.add(responses.GET, url) + + self.api.request('search/tweets', version='1.1') + + self.assertEqual(1, len(responses.calls)) + self.assertEqual(url, responses.calls[0].request.url) + + @responses.activate + def test_request_should_post_request_regardless_of_case(self): + url = 'https://api.twitter.com/1.1/statuses/update.json' + responses.add(responses.POST, url) + + self.api.request(url, method='POST') + self.api.request(url, method='post') + + self.assertEqual(2, len(responses.calls)) + self.assertEqual(url, responses.calls[0].request.url) + self.assertEqual(url, responses.calls[1].request.url) + + @responses.activate + def test_request_should_throw_exception_with_invalid_http_method(self): + #TODO(cash): should Twython catch the AttributeError and throw a TwythonError + self.assertRaises(AttributeError, self.api.request, endpoint='search/tweets', method='INVALID') + + @responses.activate + def test_request_should_encode_boolean_as_lowercase_string(self): + url = 'https://api.twitter.com/1.1/search/tweets.json' + responses.add(responses.GET, url) + + self.api.request('search/tweets', params={'include_entities': True}) + self.api.request('search/tweets', params={'include_entities': False}) + + self.assertEqual(url + '?include_entities=true', responses.calls[0].request.url) + self.assertEqual(url + '?include_entities=false', responses.calls[1].request.url) + + @responses.activate + def test_request_should_handle_string_or_number_parameter(self): + url = 'https://api.twitter.com/1.1/search/tweets.json' + responses.add(responses.GET, url) + + self.api.request('search/tweets', params={'lang': 'es'}) + self.api.request('search/tweets', params={'count': 50}) + + self.assertEqual(url + '?lang=es', responses.calls[0].request.url) + self.assertEqual(url + '?count=50', responses.calls[1].request.url) + + @responses.activate + def test_request_should_encode_string_list_as_string(self): + url = 'https://api.twitter.com/1.1/search/tweets.json' + location = ['37.781157', '-122.39872', '1mi'] + responses.add(responses.GET, url) + + self.api.request('search/tweets', params={'geocode': location}) + + self.assertEqual(url + '?geocode=37.781157%2C-122.39872%2C1mi', responses.calls[0].request.url) + + @responses.activate + def test_request_should_encode_number_list_as_string(self): + url = 'https://api.twitter.com/1.1/search/tweets.json' + location = [37.781157, -122.39872, '1mi'] + responses.add(responses.GET, url) + + self.api.request('search/tweets', params={'geocode': location}) + + self.assertEqual(url + '?geocode=37.781157%2C-122.39872%2C1mi', responses.calls[0].request.url) + + @responses.activate + def test_request_should_ignore_bad_parameter(self): + url = 'https://api.twitter.com/1.1/search/tweets.json' + responses.add(responses.GET, url) + + self.api.request('search/tweets', params={'geocode': self}) + + self.assertEqual(url, responses.calls[0].request.url) + def test_construct_api_url(self): """Test constructing a Twitter API url works as we expect""" url = 'https://api.twitter.com/1.1/search/tweets.json' From f075586fcd8364c8d448dc8a594d00d389ba729c Mon Sep 17 00:00:00 2001 From: cash Date: Sat, 11 Jan 2014 10:06:30 -0500 Subject: [PATCH 031/165] moved integration tests to new file --- tests/test_core.py | 396 ------------------------------------- tests/test_endpoints.py | 429 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 429 insertions(+), 396 deletions(-) create mode 100644 tests/test_endpoints.py diff --git a/tests/test_core.py b/tests/test_core.py index 0d701f2f..d63d40dd 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -194,399 +194,3 @@ def test_html_for_tweet_short_url(self): def test_raise_error_on_bad_ssl_cert(self): """Test TwythonError is raised by a RequestException when an actual HTTP happens""" self.assertRaises(TwythonError, self.api.get, 'https://example.com') - - # Timelines - def test_get_mentions_timeline(self): - """Test returning mentions timeline for authenticated user succeeds""" - self.api.get_mentions_timeline() - - def test_get_user_timeline(self): - """Test returning timeline for authenticated user and random user - succeeds""" - self.api.get_user_timeline() # Authenticated User Timeline - self.api.get_user_timeline(screen_name='twitter') # Random User Timeline - - def test_get_protected_user_timeline_following(self): - """Test returning a protected user timeline who you are following - succeeds""" - self.api.get_user_timeline(screen_name=protected_twitter_1) - - def test_get_protected_user_timeline_not_following(self): - """Test returning a protected user timeline who you are not following - fails and raise a TwythonAuthError""" - self.assertRaises(TwythonAuthError, self.api.get_user_timeline, - screen_name=protected_twitter_2) - - def test_retweeted_of_me(self): - """Test that getting recent tweets by authenticated user that have - been retweeted by others succeeds""" - self.api.retweeted_of_me() - - def test_get_home_timeline(self): - """Test returning home timeline for authenticated user succeeds""" - self.api.get_home_timeline() - - # Tweets - def test_get_retweets(self): - """Test getting retweets of a specific tweet succeeds""" - self.api.get_retweets(id=test_tweet_id) - - def test_show_status(self): - """Test returning a single status details succeeds""" - self.api.show_status(id=test_tweet_id) - - def test_update_and_destroy_status(self): - """Test updating and deleting a status succeeds""" - status = self.api.update_status(status='Test post just to get deleted :( %s' % int(time.time())) - self.api.destroy_status(id=status['id_str']) - - def test_get_oembed_tweet(self): - """Test getting info to embed tweet on Third Party site succeeds""" - self.api.get_oembed_tweet(id='99530515043983360') - - def test_get_retweeters_ids(self): - """Test getting ids for people who retweeted a tweet succeeds""" - self.api.get_retweeters_ids(id='99530515043983360') - - # Search - def test_search(self): - """Test searching tweets succeeds""" - self.api.search(q='twitter') - - # Direct Messages - def test_get_direct_messages(self): - """Test getting the authenticated users direct messages succeeds""" - self.api.get_direct_messages() - - def test_get_sent_messages(self): - """Test getting the authenticated users direct messages they've - sent succeeds""" - self.api.get_sent_messages() - - def test_send_get_and_destroy_direct_message(self): - """Test sending, getting, then destory a direct message succeeds""" - message = self.api.send_direct_message(screen_name=protected_twitter_1, - text='Hey d00d! %s' % int(time.time())) - - self.api.get_direct_message(id=message['id_str']) - self.api.destroy_direct_message(id=message['id_str']) - - def test_send_direct_message_to_non_follower(self): - """Test sending a direct message to someone who doesn't follow you - fails""" - self.assertRaises(TwythonError, self.api.send_direct_message, - screen_name=protected_twitter_2, text='Yo, man! %s' % int(time.time())) - - # Friends & Followers - def test_get_user_ids_of_blocked_retweets(self): - """Test that collection of user_ids that the authenticated user does - not want to receive retweets from succeeds""" - self.api.get_user_ids_of_blocked_retweets(stringify_ids=True) - - def test_get_friends_ids(self): - """Test returning ids of users the authenticated user and then a random - user is following succeeds""" - self.api.get_friends_ids() - self.api.get_friends_ids(screen_name='twitter') - - def test_get_followers_ids(self): - """Test returning ids of users the authenticated user and then a random - user are followed by succeeds""" - self.api.get_followers_ids() - self.api.get_followers_ids(screen_name='twitter') - - def test_lookup_friendships(self): - """Test returning relationships of the authenticating user to the - comma-separated list of up to 100 screen_names or user_ids provided - succeeds""" - self.api.lookup_friendships(screen_name='twitter,ryanmcgrath') - - def test_get_incoming_friendship_ids(self): - """Test returning incoming friendship ids succeeds""" - self.api.get_incoming_friendship_ids() - - def test_get_outgoing_friendship_ids(self): - """Test returning outgoing friendship ids succeeds""" - self.api.get_outgoing_friendship_ids() - - def test_create_friendship(self): - """Test creating a friendship succeeds""" - self.api.create_friendship(screen_name='justinbieber') - - def test_destroy_friendship(self): - """Test destroying a friendship succeeds""" - self.api.destroy_friendship(screen_name='justinbieber') - - def test_update_friendship(self): - """Test updating friendships succeeds""" - self.api.update_friendship(screen_name=protected_twitter_1, - retweets='true') - - self.api.update_friendship(screen_name=protected_twitter_1, - retweets=False) - - def test_show_friendships(self): - """Test showing specific friendship succeeds""" - self.api.show_friendship(target_screen_name=protected_twitter_1) - - def test_get_friends_list(self): - """Test getting list of users authenticated user then random user is - following succeeds""" - self.api.get_friends_list() - self.api.get_friends_list(screen_name='twitter') - - def test_get_followers_list(self): - """Test getting list of users authenticated user then random user are - followed by succeeds""" - self.api.get_followers_list() - self.api.get_followers_list(screen_name='twitter') - - # Users - def test_get_account_settings(self): - """Test getting the authenticated user account settings succeeds""" - self.api.get_account_settings() - - def test_verify_credentials(self): - """Test representation of the authenticated user call succeeds""" - self.api.verify_credentials() - - def test_update_account_settings(self): - """Test updating a user account settings succeeds""" - self.api.update_account_settings(lang='en') - - def test_update_delivery_service(self): - """Test updating delivery settings fails because we don't have - a mobile number on the account""" - self.assertRaises(TwythonError, self.api.update_delivery_service, - device='none') - - def test_update_profile(self): - """Test updating profile succeeds""" - self.api.update_profile(include_entities='true') - - def test_update_profile_colors(self): - """Test updating profile colors succeeds""" - self.api.update_profile_colors(profile_background_color='3D3D3D') - - def test_list_blocks(self): - """Test listing users who are blocked by the authenticated user - succeeds""" - self.api.list_blocks() - - def test_list_block_ids(self): - """Test listing user ids who are blocked by the authenticated user - succeeds""" - self.api.list_block_ids() - - def test_create_block(self): - """Test blocking a user succeeds""" - self.api.create_block(screen_name='justinbieber') - - def test_destroy_block(self): - """Test unblocking a user succeeds""" - self.api.destroy_block(screen_name='justinbieber') - - def test_lookup_user(self): - """Test listing a number of user objects succeeds""" - self.api.lookup_user(screen_name='twitter,justinbieber') - - def test_show_user(self): - """Test showing one user works""" - self.api.show_user(screen_name='twitter') - - def test_search_users(self): - """Test that searching for users succeeds""" - self.api.search_users(q='Twitter API') - - def test_get_contributees(self): - """Test returning list of accounts the specified user can - contribute to succeeds""" - self.api.get_contributees(screen_name='TechCrunch') - - def test_get_contributors(self): - """Test returning list of accounts that contribute to the - authenticated user fails because we are not a Contributor account""" - self.assertRaises(TwythonError, self.api.get_contributors, - screen_name=screen_name) - - def test_remove_profile_banner(self): - """Test removing profile banner succeeds""" - self.api.remove_profile_banner() - - def test_get_profile_banner_sizes(self): - """Test getting list of profile banner sizes fails because - we have not uploaded a profile banner""" - self.assertRaises(TwythonError, self.api.get_profile_banner_sizes) - - # Suggested Users - def test_get_user_suggestions_by_slug(self): - """Test getting user suggestions by slug succeeds""" - self.api.get_user_suggestions_by_slug(slug='twitter') - - def test_get_user_suggestions(self): - """Test getting user suggestions succeeds""" - self.api.get_user_suggestions() - - def test_get_user_suggestions_statuses_by_slug(self): - """Test getting status of suggested users succeeds""" - self.api.get_user_suggestions_statuses_by_slug(slug='funny') - - # Favorites - def test_get_favorites(self): - """Test getting list of favorites for the authenticated - user succeeds""" - self.api.get_favorites() - - def test_create_and_destroy_favorite(self): - """Test creating and destroying a favorite on a tweet succeeds""" - self.api.create_favorite(id=test_tweet_id) - self.api.destroy_favorite(id=test_tweet_id) - - # Lists - def test_show_lists(self): - """Test show lists for specified user""" - self.api.show_lists(screen_name='twitter') - - def test_get_list_statuses(self): - """Test timeline of tweets authored by members of the - specified list succeeds""" - self.api.get_list_statuses(slug=test_list_slug, - owner_screen_name=test_list_owner_screen_name) - - def test_create_update_destroy_list_add_remove_list_members(self): - """Test create a list, adding and removing members then - deleting the list succeeds""" - the_list = self.api.create_list(name='Stuff %s' % int(time.time())) - list_id = the_list['id_str'] - - self.api.update_list(list_id=list_id, name='Stuff Renamed %s' % int(time.time())) - - screen_names = ['johncena', 'xbox'] - # Multi add/delete members - self.api.create_list_members(list_id=list_id, - screen_name=screen_names) - self.api.delete_list_members(list_id=list_id, - screen_name=screen_names) - - # Single add/delete member - self.api.add_list_member(list_id=list_id, screen_name='justinbieber') - self.api.delete_list_member(list_id=list_id, screen_name='justinbieber') - - self.api.delete_list(list_id=list_id) - - def test_get_list_memberships(self): - """Test list of memberhips the authenticated user succeeds""" - self.api.get_list_memberships() - - def test_get_list_subscribers(self): - """Test list of subscribers of a specific list succeeds""" - self.api.get_list_subscribers(slug=test_list_slug, - owner_screen_name=test_list_owner_screen_name) - - def test_subscribe_is_subbed_and_unsubscribe_to_list(self): - """Test subscribing, is a list sub and unsubbing to list succeeds""" - self.api.subscribe_to_list(slug=test_list_slug, - owner_screen_name=test_list_owner_screen_name) - # Returns 404 if user is not a subscriber - self.api.is_list_subscriber(slug=test_list_slug, - owner_screen_name=test_list_owner_screen_name, - screen_name=screen_name) - self.api.unsubscribe_from_list(slug=test_list_slug, - owner_screen_name=test_list_owner_screen_name) - - def test_is_list_member(self): - """Test returning if specified user is member of a list succeeds""" - # Returns 404 if not list member - self.api.is_list_member(slug=test_list_slug, - owner_screen_name=test_list_owner_screen_name, - screen_name='themattharris') - - def test_get_list_members(self): - """Test listing members of the specified list succeeds""" - self.api.get_list_members(slug=test_list_slug, - owner_screen_name=test_list_owner_screen_name) - - def test_get_specific_list(self): - """Test getting specific list succeeds""" - self.api.get_specific_list(slug=test_list_slug, - owner_screen_name=test_list_owner_screen_name) - - def test_get_list_subscriptions(self): - """Test collection of the lists the specified user is - subscribed to succeeds""" - self.api.get_list_subscriptions(screen_name='twitter') - - def test_show_owned_lists(self): - """Test collection of lists the specified user owns succeeds""" - self.api.show_owned_lists(screen_name='twitter') - - # Saved Searches - def test_get_saved_searches(self): - """Test getting list of saved searches for authenticated - user succeeds""" - self.api.get_saved_searches() - - def test_create_get_destroy_saved_search(self): - """Test getting list of saved searches for authenticated - user succeeds""" - saved_search = self.api.create_saved_search(query='#Twitter') - saved_search_id = saved_search['id_str'] - - self.api.show_saved_search(id=saved_search_id) - self.api.destroy_saved_search(id=saved_search_id) - - # Places & Geo - def test_get_geo_info(self): - """Test getting info about a geo location succeeds""" - self.api.get_geo_info(place_id='df51dec6f4ee2b2c') - - def test_reverse_geo_code(self): - """Test reversing geocode succeeds""" - self.api.reverse_geocode(lat='37.76893497', long='-122.42284884') - - def test_search_geo(self): - """Test search for places that can be attached - to a statuses/update succeeds""" - self.api.search_geo(query='Toronto') - - def test_get_similar_places(self): - """Test locates places near the given coordinates which - are similar in name succeeds""" - self.api.get_similar_places(lat='37', long='-122', name='Twitter HQ') - - # Trends - def test_get_place_trends(self): - """Test getting the top 10 trending topics for a specific - WOEID succeeds""" - self.api.get_place_trends(id=1) - - def test_get_available_trends(self): - """Test returning locations that Twitter has trending - topic information for succeeds""" - self.api.get_available_trends() - - def test_get_closest_trends(self): - """Test getting the locations that Twitter has trending topic - information for, closest to a specified location succeeds""" - self.api.get_closest_trends(lat='37', long='-122') - - # Help - def test_get_twitter_configuration(self): - """Test getting Twitter's configuration succeeds""" - self.api.get_twitter_configuration() - - def test_get_supported_languages(self): - """Test getting languages supported by Twitter succeeds""" - self.api.get_supported_languages() - - def test_privacy_policy(self): - """Test getting Twitter's Privacy Policy succeeds""" - self.api.get_privacy_policy() - - def test_get_tos(self): - """Test getting the Twitter Terms of Service succeeds""" - self.api.get_tos() - - def test_get_application_rate_limit_status(self): - """Test getting application rate limit status succeeds""" - self.oauth2_api.get_application_rate_limit_status() diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py new file mode 100644 index 00000000..f5ea56ac --- /dev/null +++ b/tests/test_endpoints.py @@ -0,0 +1,429 @@ +from twython import Twython, TwythonError, TwythonAuthError + +from .config import ( + app_key, app_secret, oauth_token, oauth_token_secret, + protected_twitter_1, protected_twitter_2, screen_name, + test_tweet_id, test_list_slug, test_list_owner_screen_name, + access_token, test_tweet_object, test_tweet_html +) + +import time +import unittest + + +class TwythonEndpointsTestCase(unittest.TestCase): + def setUp(self): + + client_args = { + 'headers': { + 'User-Agent': '__twython__ Test' + }, + 'allow_redirects': False + } + + oauth2_client_args = { + 'headers': {} # This is so we can hit coverage that Twython sets User-Agent for us if none is supplied + } + + self.api = Twython(app_key, app_secret, + oauth_token, oauth_token_secret, + client_args=client_args) + + self.oauth2_api = Twython(app_key, access_token=access_token, + client_args=oauth2_client_args) + + # Timelines + def test_get_mentions_timeline(self): + """Test returning mentions timeline for authenticated user succeeds""" + self.api.get_mentions_timeline() + + def test_get_user_timeline(self): + """Test returning timeline for authenticated user and random user + succeeds""" + self.api.get_user_timeline() # Authenticated User Timeline + self.api.get_user_timeline(screen_name='twitter') # Random User Timeline + + def test_get_protected_user_timeline_following(self): + """Test returning a protected user timeline who you are following + succeeds""" + self.api.get_user_timeline(screen_name=protected_twitter_1) + + def test_get_protected_user_timeline_not_following(self): + """Test returning a protected user timeline who you are not following + fails and raise a TwythonAuthError""" + self.assertRaises(TwythonAuthError, self.api.get_user_timeline, + screen_name=protected_twitter_2) + + def test_retweeted_of_me(self): + """Test that getting recent tweets by authenticated user that have + been retweeted by others succeeds""" + self.api.retweeted_of_me() + + def test_get_home_timeline(self): + """Test returning home timeline for authenticated user succeeds""" + self.api.get_home_timeline() + + # Tweets + def test_get_retweets(self): + """Test getting retweets of a specific tweet succeeds""" + self.api.get_retweets(id=test_tweet_id) + + def test_show_status(self): + """Test returning a single status details succeeds""" + self.api.show_status(id=test_tweet_id) + + def test_update_and_destroy_status(self): + """Test updating and deleting a status succeeds""" + status = self.api.update_status(status='Test post just to get deleted :( %s' % int(time.time())) + self.api.destroy_status(id=status['id_str']) + + def test_get_oembed_tweet(self): + """Test getting info to embed tweet on Third Party site succeeds""" + self.api.get_oembed_tweet(id='99530515043983360') + + def test_get_retweeters_ids(self): + """Test getting ids for people who retweeted a tweet succeeds""" + self.api.get_retweeters_ids(id='99530515043983360') + + # Search + def test_search(self): + """Test searching tweets succeeds""" + self.api.search(q='twitter') + + # Direct Messages + def test_get_direct_messages(self): + """Test getting the authenticated users direct messages succeeds""" + self.api.get_direct_messages() + + def test_get_sent_messages(self): + """Test getting the authenticated users direct messages they've + sent succeeds""" + self.api.get_sent_messages() + + def test_send_get_and_destroy_direct_message(self): + """Test sending, getting, then destory a direct message succeeds""" + message = self.api.send_direct_message(screen_name=protected_twitter_1, + text='Hey d00d! %s' % int(time.time())) + + self.api.get_direct_message(id=message['id_str']) + self.api.destroy_direct_message(id=message['id_str']) + + def test_send_direct_message_to_non_follower(self): + """Test sending a direct message to someone who doesn't follow you + fails""" + self.assertRaises(TwythonError, self.api.send_direct_message, + screen_name=protected_twitter_2, text='Yo, man! %s' % int(time.time())) + + # Friends & Followers + def test_get_user_ids_of_blocked_retweets(self): + """Test that collection of user_ids that the authenticated user does + not want to receive retweets from succeeds""" + self.api.get_user_ids_of_blocked_retweets(stringify_ids=True) + + def test_get_friends_ids(self): + """Test returning ids of users the authenticated user and then a random + user is following succeeds""" + self.api.get_friends_ids() + self.api.get_friends_ids(screen_name='twitter') + + def test_get_followers_ids(self): + """Test returning ids of users the authenticated user and then a random + user are followed by succeeds""" + self.api.get_followers_ids() + self.api.get_followers_ids(screen_name='twitter') + + def test_lookup_friendships(self): + """Test returning relationships of the authenticating user to the + comma-separated list of up to 100 screen_names or user_ids provided + succeeds""" + self.api.lookup_friendships(screen_name='twitter,ryanmcgrath') + + def test_get_incoming_friendship_ids(self): + """Test returning incoming friendship ids succeeds""" + self.api.get_incoming_friendship_ids() + + def test_get_outgoing_friendship_ids(self): + """Test returning outgoing friendship ids succeeds""" + self.api.get_outgoing_friendship_ids() + + def test_create_friendship(self): + """Test creating a friendship succeeds""" + self.api.create_friendship(screen_name='justinbieber') + + def test_destroy_friendship(self): + """Test destroying a friendship succeeds""" + self.api.destroy_friendship(screen_name='justinbieber') + + def test_update_friendship(self): + """Test updating friendships succeeds""" + self.api.update_friendship(screen_name=protected_twitter_1, + retweets='true') + + self.api.update_friendship(screen_name=protected_twitter_1, + retweets=False) + + def test_show_friendships(self): + """Test showing specific friendship succeeds""" + self.api.show_friendship(target_screen_name=protected_twitter_1) + + def test_get_friends_list(self): + """Test getting list of users authenticated user then random user is + following succeeds""" + self.api.get_friends_list() + self.api.get_friends_list(screen_name='twitter') + + def test_get_followers_list(self): + """Test getting list of users authenticated user then random user are + followed by succeeds""" + self.api.get_followers_list() + self.api.get_followers_list(screen_name='twitter') + + # Users + def test_get_account_settings(self): + """Test getting the authenticated user account settings succeeds""" + self.api.get_account_settings() + + def test_verify_credentials(self): + """Test representation of the authenticated user call succeeds""" + self.api.verify_credentials() + + def test_update_account_settings(self): + """Test updating a user account settings succeeds""" + self.api.update_account_settings(lang='en') + + def test_update_delivery_service(self): + """Test updating delivery settings fails because we don't have + a mobile number on the account""" + self.assertRaises(TwythonError, self.api.update_delivery_service, + device='none') + + def test_update_profile(self): + """Test updating profile succeeds""" + self.api.update_profile(include_entities='true') + + def test_update_profile_colors(self): + """Test updating profile colors succeeds""" + self.api.update_profile_colors(profile_background_color='3D3D3D') + + def test_list_blocks(self): + """Test listing users who are blocked by the authenticated user + succeeds""" + self.api.list_blocks() + + def test_list_block_ids(self): + """Test listing user ids who are blocked by the authenticated user + succeeds""" + self.api.list_block_ids() + + def test_create_block(self): + """Test blocking a user succeeds""" + self.api.create_block(screen_name='justinbieber') + + def test_destroy_block(self): + """Test unblocking a user succeeds""" + self.api.destroy_block(screen_name='justinbieber') + + def test_lookup_user(self): + """Test listing a number of user objects succeeds""" + self.api.lookup_user(screen_name='twitter,justinbieber') + + def test_show_user(self): + """Test showing one user works""" + self.api.show_user(screen_name='twitter') + + def test_search_users(self): + """Test that searching for users succeeds""" + self.api.search_users(q='Twitter API') + + def test_get_contributees(self): + """Test returning list of accounts the specified user can + contribute to succeeds""" + self.api.get_contributees(screen_name='TechCrunch') + + def test_get_contributors(self): + """Test returning list of accounts that contribute to the + authenticated user fails because we are not a Contributor account""" + self.assertRaises(TwythonError, self.api.get_contributors, + screen_name=screen_name) + + def test_remove_profile_banner(self): + """Test removing profile banner succeeds""" + self.api.remove_profile_banner() + + def test_get_profile_banner_sizes(self): + """Test getting list of profile banner sizes fails because + we have not uploaded a profile banner""" + self.assertRaises(TwythonError, self.api.get_profile_banner_sizes) + + # Suggested Users + def test_get_user_suggestions_by_slug(self): + """Test getting user suggestions by slug succeeds""" + self.api.get_user_suggestions_by_slug(slug='twitter') + + def test_get_user_suggestions(self): + """Test getting user suggestions succeeds""" + self.api.get_user_suggestions() + + def test_get_user_suggestions_statuses_by_slug(self): + """Test getting status of suggested users succeeds""" + self.api.get_user_suggestions_statuses_by_slug(slug='funny') + + # Favorites + def test_get_favorites(self): + """Test getting list of favorites for the authenticated + user succeeds""" + self.api.get_favorites() + + def test_create_and_destroy_favorite(self): + """Test creating and destroying a favorite on a tweet succeeds""" + self.api.create_favorite(id=test_tweet_id) + self.api.destroy_favorite(id=test_tweet_id) + + # Lists + def test_show_lists(self): + """Test show lists for specified user""" + self.api.show_lists(screen_name='twitter') + + def test_get_list_statuses(self): + """Test timeline of tweets authored by members of the + specified list succeeds""" + self.api.get_list_statuses(slug=test_list_slug, + owner_screen_name=test_list_owner_screen_name) + + def test_create_update_destroy_list_add_remove_list_members(self): + """Test create a list, adding and removing members then + deleting the list succeeds""" + the_list = self.api.create_list(name='Stuff %s' % int(time.time())) + list_id = the_list['id_str'] + + self.api.update_list(list_id=list_id, name='Stuff Renamed %s' % int(time.time())) + + screen_names = ['johncena', 'xbox'] + # Multi add/delete members + self.api.create_list_members(list_id=list_id, + screen_name=screen_names) + self.api.delete_list_members(list_id=list_id, + screen_name=screen_names) + + # Single add/delete member + self.api.add_list_member(list_id=list_id, screen_name='justinbieber') + self.api.delete_list_member(list_id=list_id, screen_name='justinbieber') + + self.api.delete_list(list_id=list_id) + + def test_get_list_memberships(self): + """Test list of memberhips the authenticated user succeeds""" + self.api.get_list_memberships() + + def test_get_list_subscribers(self): + """Test list of subscribers of a specific list succeeds""" + self.api.get_list_subscribers(slug=test_list_slug, + owner_screen_name=test_list_owner_screen_name) + + def test_subscribe_is_subbed_and_unsubscribe_to_list(self): + """Test subscribing, is a list sub and unsubbing to list succeeds""" + self.api.subscribe_to_list(slug=test_list_slug, + owner_screen_name=test_list_owner_screen_name) + # Returns 404 if user is not a subscriber + self.api.is_list_subscriber(slug=test_list_slug, + owner_screen_name=test_list_owner_screen_name, + screen_name=screen_name) + self.api.unsubscribe_from_list(slug=test_list_slug, + owner_screen_name=test_list_owner_screen_name) + + def test_is_list_member(self): + """Test returning if specified user is member of a list succeeds""" + # Returns 404 if not list member + self.api.is_list_member(slug=test_list_slug, + owner_screen_name=test_list_owner_screen_name, + screen_name='themattharris') + + def test_get_list_members(self): + """Test listing members of the specified list succeeds""" + self.api.get_list_members(slug=test_list_slug, + owner_screen_name=test_list_owner_screen_name) + + def test_get_specific_list(self): + """Test getting specific list succeeds""" + self.api.get_specific_list(slug=test_list_slug, + owner_screen_name=test_list_owner_screen_name) + + def test_get_list_subscriptions(self): + """Test collection of the lists the specified user is + subscribed to succeeds""" + self.api.get_list_subscriptions(screen_name='twitter') + + def test_show_owned_lists(self): + """Test collection of lists the specified user owns succeeds""" + self.api.show_owned_lists(screen_name='twitter') + + # Saved Searches + def test_get_saved_searches(self): + """Test getting list of saved searches for authenticated + user succeeds""" + self.api.get_saved_searches() + + def test_create_get_destroy_saved_search(self): + """Test getting list of saved searches for authenticated + user succeeds""" + saved_search = self.api.create_saved_search(query='#Twitter') + saved_search_id = saved_search['id_str'] + + self.api.show_saved_search(id=saved_search_id) + self.api.destroy_saved_search(id=saved_search_id) + + # Places & Geo + def test_get_geo_info(self): + """Test getting info about a geo location succeeds""" + self.api.get_geo_info(place_id='df51dec6f4ee2b2c') + + def test_reverse_geo_code(self): + """Test reversing geocode succeeds""" + self.api.reverse_geocode(lat='37.76893497', long='-122.42284884') + + def test_search_geo(self): + """Test search for places that can be attached + to a statuses/update succeeds""" + self.api.search_geo(query='Toronto') + + def test_get_similar_places(self): + """Test locates places near the given coordinates which + are similar in name succeeds""" + self.api.get_similar_places(lat='37', long='-122', name='Twitter HQ') + + # Trends + def test_get_place_trends(self): + """Test getting the top 10 trending topics for a specific + WOEID succeeds""" + self.api.get_place_trends(id=1) + + def test_get_available_trends(self): + """Test returning locations that Twitter has trending + topic information for succeeds""" + self.api.get_available_trends() + + def test_get_closest_trends(self): + """Test getting the locations that Twitter has trending topic + information for, closest to a specified location succeeds""" + self.api.get_closest_trends(lat='37', long='-122') + + # Help + def test_get_twitter_configuration(self): + """Test getting Twitter's configuration succeeds""" + self.api.get_twitter_configuration() + + def test_get_supported_languages(self): + """Test getting languages supported by Twitter succeeds""" + self.api.get_supported_languages() + + def test_privacy_policy(self): + """Test getting Twitter's Privacy Policy succeeds""" + self.api.get_privacy_policy() + + def test_get_tos(self): + """Test getting the Twitter Terms of Service succeeds""" + self.api.get_tos() + + def test_get_application_rate_limit_status(self): + """Test getting application rate limit status succeeds""" + self.oauth2_api.get_application_rate_limit_status() From 5304803f0955b1d0f4ef8457334f500402b60dee Mon Sep 17 00:00:00 2001 From: cash Date: Sat, 11 Jan 2014 11:13:55 -0500 Subject: [PATCH 032/165] added docstrings for new tests and finished testing input arguments of request() --- tests/test_core.py | 95 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 23 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index d63d40dd..b0cdc8da 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -11,6 +11,11 @@ import unittest import responses +try: + import io.StringIO as StringIO +except ImportError: + import StringIO + class TwythonAPITestCase(unittest.TestCase): def setUp(self): @@ -33,8 +38,13 @@ def setUp(self): self.oauth2_api = Twython(app_key, access_token=access_token, client_args=oauth2_client_args) + def get_url(self, endpoint): + """Convenience function for mapping from endpoint to URL""" + return '%s/%s.json' % (self.api.api_url % self.api.api_version, endpoint) + @responses.activate def test_request_should_handle_full_endpoint(self): + """Test that request() accepts a full URL for the endpoint argument""" url = 'https://api.twitter.com/1.1/search/tweets.json' responses.add(responses.GET, url) @@ -45,16 +55,18 @@ def test_request_should_handle_full_endpoint(self): @responses.activate def test_request_should_handle_relative_endpoint(self): - url = 'https://api.twitter.com/1.1/search/tweets.json' + """Test that request() accepts a twitter endpoint name for the endpoint argument""" + url = 'https://api.twitter.com/2.0/search/tweets.json' responses.add(responses.GET, url) - self.api.request('search/tweets', version='1.1') + self.api.request('search/tweets', version='2.0') self.assertEqual(1, len(responses.calls)) self.assertEqual(url, responses.calls[0].request.url) @responses.activate def test_request_should_post_request_regardless_of_case(self): + """Test that request() accepts the HTTP method name regardless of case""" url = 'https://api.twitter.com/1.1/statuses/update.json' responses.add(responses.POST, url) @@ -62,70 +74,101 @@ def test_request_should_post_request_regardless_of_case(self): self.api.request(url, method='post') self.assertEqual(2, len(responses.calls)) - self.assertEqual(url, responses.calls[0].request.url) - self.assertEqual(url, responses.calls[1].request.url) + self.assertEqual('POST', responses.calls[0].request.method) + self.assertEqual('POST', responses.calls[1].request.method) @responses.activate def test_request_should_throw_exception_with_invalid_http_method(self): + """Test that request() throws an exception when an invalid HTTP method is passed""" #TODO(cash): should Twython catch the AttributeError and throw a TwythonError self.assertRaises(AttributeError, self.api.request, endpoint='search/tweets', method='INVALID') @responses.activate def test_request_should_encode_boolean_as_lowercase_string(self): - url = 'https://api.twitter.com/1.1/search/tweets.json' + """Test that request() encodes a boolean parameter as a lowercase string""" + endpoint = 'search/tweets' + url = self.get_url(endpoint) responses.add(responses.GET, url) - self.api.request('search/tweets', params={'include_entities': True}) - self.api.request('search/tweets', params={'include_entities': False}) + self.api.request(endpoint, params={'include_entities': True}) + self.api.request(endpoint, params={'include_entities': False}) self.assertEqual(url + '?include_entities=true', responses.calls[0].request.url) self.assertEqual(url + '?include_entities=false', responses.calls[1].request.url) @responses.activate def test_request_should_handle_string_or_number_parameter(self): - url = 'https://api.twitter.com/1.1/search/tweets.json' + """Test that request() encodes a numeric or string parameter correctly""" + endpoint = 'search/tweets' + url = self.get_url(endpoint) responses.add(responses.GET, url) - self.api.request('search/tweets', params={'lang': 'es'}) - self.api.request('search/tweets', params={'count': 50}) + self.api.request(endpoint, params={'lang': 'es'}) + self.api.request(endpoint, params={'count': 50}) self.assertEqual(url + '?lang=es', responses.calls[0].request.url) self.assertEqual(url + '?count=50', responses.calls[1].request.url) @responses.activate - def test_request_should_encode_string_list_as_string(self): - url = 'https://api.twitter.com/1.1/search/tweets.json' + def test_request_should_encode_list_of_strings_as_string(self): + """Test that request() encodes a list of strings as a comma-separated string""" + endpoint = 'search/tweets' + url = self.get_url(endpoint) location = ['37.781157', '-122.39872', '1mi'] responses.add(responses.GET, url) - self.api.request('search/tweets', params={'geocode': location}) + self.api.request(endpoint, params={'geocode': location}) + # requests url encodes the parameters so , is %2C self.assertEqual(url + '?geocode=37.781157%2C-122.39872%2C1mi', responses.calls[0].request.url) @responses.activate - def test_request_should_encode_number_list_as_string(self): - url = 'https://api.twitter.com/1.1/search/tweets.json' + def test_request_should_encode_numeric_list_as_string(self): + """Test that request() encodes a list of numbers as a comma-separated string""" + endpoint = 'search/tweets' + url = self.get_url(endpoint) location = [37.781157, -122.39872, '1mi'] responses.add(responses.GET, url) - self.api.request('search/tweets', params={'geocode': location}) + self.api.request(endpoint, params={'geocode': location}) self.assertEqual(url + '?geocode=37.781157%2C-122.39872%2C1mi', responses.calls[0].request.url) @responses.activate def test_request_should_ignore_bad_parameter(self): - url = 'https://api.twitter.com/1.1/search/tweets.json' + """Test that request() ignores unexpected parameter types""" + endpoint = 'search/tweets' + url = self.get_url(endpoint) responses.add(responses.GET, url) - self.api.request('search/tweets', params={'geocode': self}) + self.api.request(endpoint, params={'geocode': self}) self.assertEqual(url, responses.calls[0].request.url) - def test_construct_api_url(self): - """Test constructing a Twitter API url works as we expect""" - url = 'https://api.twitter.com/1.1/search/tweets.json' - constructed_url = self.api.construct_api_url(url, q='#twitter') - self.assertEqual(constructed_url, 'https://api.twitter.com/1.1/search/tweets.json?q=%23twitter') + @responses.activate + def test_request_should_handle_file_as_parameter(self): + """Test that request() pulls a file out of params for requests lib""" + endpoint = 'account/update_profile_image' + url = self.get_url(endpoint) + responses.add(responses.POST, url) + + mock_file = StringIO.StringIO("Twython test image") + self.api.request(endpoint, method='POST', params={'image': mock_file}) + + self.assertIn('filename="image"', responses.calls[0].request.body) + self.assertIn("Twython test image", responses.calls[0].request.body) + + @responses.activate + def test_request_should_put_params_in_body_when_post(self): + """Test that request() passes params as data when the request is a POST""" + endpoint = 'statuses/update' + url = self.get_url(endpoint) + responses.add(responses.POST, url) + + self.api.request(endpoint, method='POST', params={'status': 'this is a test'}) + + self.assertIn('status=this+is+a+test', responses.calls[0].request.body) + self.assertNotIn('status=this+is+a+test', responses.calls[0].request.url) def test_get(self): """Test Twython generic GET request works""" @@ -138,6 +181,12 @@ def test_post(self): status = self.api.post(update_url, params={'status': 'I love Twython! %s' % int(time.time())}) self.api.post('statuses/destroy/%s' % status['id_str']) + def test_construct_api_url(self): + """Test constructing a Twitter API url works as we expect""" + url = 'https://api.twitter.com/1.1/search/tweets.json' + constructed_url = self.api.construct_api_url(url, q='#twitter') + self.assertEqual(constructed_url, 'https://api.twitter.com/1.1/search/tweets.json?q=%23twitter') + def test_get_lastfunction_header(self): """Test getting last specific header of the last API call works""" self.api.get('statuses/home_timeline') From fc55791cbfb6d3c5f190d5c985afcd14e7daaad8 Mon Sep 17 00:00:00 2001 From: cash Date: Sat, 11 Jan 2014 15:37:47 -0500 Subject: [PATCH 033/165] updated remaining tests in test_core.py and removed cursor test (which will be replaced) --- tests/test_core.py | 124 +++++++++++++++++++++------------------------ 1 file changed, 58 insertions(+), 66 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index b0cdc8da..e09556d4 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,42 +1,27 @@ from twython import Twython, TwythonError, TwythonAuthError from .config import ( - app_key, app_secret, oauth_token, oauth_token_secret, - protected_twitter_1, protected_twitter_2, screen_name, - test_tweet_id, test_list_slug, test_list_owner_screen_name, - access_token, test_tweet_object, test_tweet_html + test_tweet_object, test_tweet_html ) -import time import unittest import responses +import requests try: import io.StringIO as StringIO except ImportError: import StringIO +try: + import unittest.mock as mock +except ImportError: + import mock + class TwythonAPITestCase(unittest.TestCase): def setUp(self): - - client_args = { - 'headers': { - 'User-Agent': '__twython__ Test' - }, - 'allow_redirects': False - } - - oauth2_client_args = { - 'headers': {} # This is so we can hit coverage that Twython sets User-Agent for us if none is supplied - } - - self.api = Twython(app_key, app_secret, - oauth_token, oauth_token_secret, - client_args=client_args) - - self.oauth2_api = Twython(app_key, access_token=access_token, - client_args=oauth2_client_args) + self.api = Twython('', '', '', '') def get_url(self, endpoint): """Convenience function for mapping from endpoint to URL""" @@ -170,52 +155,63 @@ def test_request_should_put_params_in_body_when_post(self): self.assertIn('status=this+is+a+test', responses.calls[0].request.body) self.assertNotIn('status=this+is+a+test', responses.calls[0].request.url) - def test_get(self): + @responses.activate + def test_get_uses_get_method(self): """Test Twython generic GET request works""" - self.api.get('account/verify_credentials') + endpoint = 'account/verify_credentials' + url = self.get_url(endpoint) + responses.add(responses.GET, url) - def test_post(self): - """Test Twython generic POST request works, with a full url and - with just an endpoint""" - update_url = 'https://api.twitter.com/1.1/statuses/update.json' - status = self.api.post(update_url, params={'status': 'I love Twython! %s' % int(time.time())}) - self.api.post('statuses/destroy/%s' % status['id_str']) + self.api.get(endpoint) - def test_construct_api_url(self): - """Test constructing a Twitter API url works as we expect""" - url = 'https://api.twitter.com/1.1/search/tweets.json' - constructed_url = self.api.construct_api_url(url, q='#twitter') - self.assertEqual(constructed_url, 'https://api.twitter.com/1.1/search/tweets.json?q=%23twitter') + self.assertEqual(1, len(responses.calls)) + self.assertEqual(url, responses.calls[0].request.url) - def test_get_lastfunction_header(self): + @responses.activate + def test_post_uses_post_method(self): + """Test Twython generic POST request works""" + endpoint = 'statuses/update' + url = self.get_url(endpoint) + responses.add(responses.POST, url) + + self.api.post(endpoint, params={'status': 'I love Twython!'}) + + self.assertEqual(1, len(responses.calls)) + self.assertEqual(url, responses.calls[0].request.url) + + def test_raise_twython_error_on_request_exception(self): + """Test if TwythonError is raised by a RequestException""" + with mock.patch.object(requests.Session, 'get') as get_mock: + # mocking an ssl cert error + get_mock.side_effect = requests.RequestException("hostname 'example.com' doesn't match ...") + self.assertRaises(TwythonError, self.api.get, 'https://example.com') + + @responses.activate + def test_get_lastfunction_header_should_return_header(self): """Test getting last specific header of the last API call works""" - self.api.get('statuses/home_timeline') - self.api.get_lastfunction_header('x-rate-limit-remaining') + endpoint = 'statuses/home_timeline' + url = self.get_url(endpoint) + responses.add(responses.GET, url, adding_headers={'x-rate-limit-remaining': 37}) + + self.api.get(endpoint) - def test_get_lastfunction_header_not_present(self): - """Test getting specific header that does not exist from the last call returns None""" - self.api.get('statuses/home_timeline') - header = self.api.get_lastfunction_header('does-not-exist') - self.assertEqual(header, None) + value = self.api.get_lastfunction_header('x-rate-limit-remaining') + self.assertEqual(37, value) + value2 = self.api.get_lastfunction_header('does-not-exist') + self.assertIsNone(value2) + value3 = self.api.get_lastfunction_header('not-there-either', 96) + self.assertEqual(96, value3) - def test_get_lastfunction_header_no_last_api_call(self): + def test_get_lastfunction_header_should_raise_error_when_no_previous_call(self): """Test attempting to get a header when no API call was made raises a TwythonError""" - self.assertRaises(TwythonError, self.api.get_lastfunction_header, - 'no-api-call-was-made') - - def test_cursor(self): - """Test looping through the generator results works, at least once that is""" - search = self.api.cursor(self.api.search, q='twitter', count=1) - counter = 0 - while counter < 2: - counter += 1 - result = next(search) - new_id_str = int(result['id_str']) - if counter == 1: - prev_id_str = new_id_str - time.sleep(1) # Give time for another tweet to come into search - if counter == 2: - self.assertTrue(new_id_str > prev_id_str) + self.assertRaises(TwythonError, self.api.get_lastfunction_header, 'no-api-call-was-made') + + # Static methods + def test_construct_api_url(self): + """Test constructing a Twitter API url works as we expect""" + url = 'https://api.twitter.com/1.1/search/tweets.json' + constructed_url = self.api.construct_api_url(url, q='#twitter') + self.assertEqual(constructed_url, 'https://api.twitter.com/1.1/search/tweets.json?q=%23twitter') def test_encode(self): """Test encoding UTF-8 works""" @@ -236,10 +232,6 @@ def test_html_for_tweet_expanded_url(self): def test_html_for_tweet_short_url(self): """Test using expanded url in HTML for Tweet displays full urls""" tweet_text = self.api.html_for_tweet(test_tweet_object, False) - # Make sure HTML doesn't contain the display OR exapanded url + # Make sure HTML doesn't contain the display OR expanded url self.assertTrue(not 'http://google.com' in tweet_text) self.assertTrue(not 'google.com' in tweet_text) - - def test_raise_error_on_bad_ssl_cert(self): - """Test TwythonError is raised by a RequestException when an actual HTTP happens""" - self.assertRaises(TwythonError, self.api.get, 'https://example.com') From c449e3f8e185be383f07c652561433f22b073838 Mon Sep 17 00:00:00 2001 From: cash Date: Sat, 11 Jan 2014 15:45:45 -0500 Subject: [PATCH 034/165] skipping tests that were failing because of external dependency on Twitter --- tests/test_auth.py | 9 +++++ tests/test_core.py | 6 +++- tests/test_endpoints.py | 75 +++++++++++++++++++++++++++++++++++++++++ tests/test_streaming.py | 5 +++ 4 files changed, 94 insertions(+), 1 deletion(-) diff --git a/tests/test_auth.py b/tests/test_auth.py index edefe07b..3b2a7133 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -16,48 +16,57 @@ def setUp(self): self.oauth2_bad_api = Twython('BAD_APP_KEY', 'BAD_APP_SECRET', oauth_version=2) + @unittest.skip('skipping non-updated test') def test_get_authentication_tokens(self): """Test getting authentication tokens works""" self.api.get_authentication_tokens(callback_url='http://google.com/', force_login=True, screen_name=screen_name) + @unittest.skip('skipping non-updated test') def test_get_authentication_tokens_bad_tokens(self): """Test getting authentication tokens with bad tokens raises TwythonAuthError""" self.assertRaises(TwythonAuthError, self.bad_api.get_authentication_tokens, callback_url='http://google.com/') + @unittest.skip('skipping non-updated test') def test_get_authorized_tokens_bad_tokens(self): """Test getting final tokens fails with wrong tokens""" self.assertRaises(TwythonError, self.bad_api.get_authorized_tokens, 'BAD_OAUTH_VERIFIER') + @unittest.skip('skipping non-updated test') def test_get_authorized_tokens_invalid_or_expired_tokens(self): """Test getting final token fails when invalid or expired tokens have been passed""" self.assertRaises(TwythonError, self.bad_api_invalid_tokens.get_authorized_tokens, 'BAD_OAUTH_VERIFIER') + @unittest.skip('skipping non-updated test') def test_get_authentication_tokens_raises_error_when_oauth2(self): """Test when API is set for OAuth 2, get_authentication_tokens raises a TwythonError""" self.assertRaises(TwythonError, self.oauth2_api.get_authentication_tokens) + @unittest.skip('skipping non-updated test') def test_get_authorization_tokens_raises_error_when_oauth2(self): """Test when API is set for OAuth 2, get_authorized_tokens raises a TwythonError""" self.assertRaises(TwythonError, self.oauth2_api.get_authorized_tokens, 'BAD_OAUTH_VERIFIER') + @unittest.skip('skipping non-updated test') def test_obtain_access_token(self): """Test obtaining an Application Only OAuth 2 access token succeeds""" self.oauth2_api.obtain_access_token() + @unittest.skip('skipping non-updated test') def test_obtain_access_token_bad_tokens(self): """Test obtaining an Application Only OAuth 2 access token using bad app tokens fails""" self.assertRaises(TwythonAuthError, self.oauth2_bad_api.obtain_access_token) + @unittest.skip('skipping non-updated test') def test_obtain_access_token_raises_error_when_oauth1(self): """Test when API is set for OAuth 1, obtain_access_token raises a TwythonError""" diff --git a/tests/test_core.py b/tests/test_core.py index e09556d4..17d1fceb 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4,7 +4,11 @@ test_tweet_object, test_tweet_html ) -import unittest +try: + import unittest2 as unittest +except ImportError: + import unittest + import responses import requests diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index f5ea56ac..edeacfa1 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -33,73 +33,88 @@ def setUp(self): client_args=oauth2_client_args) # Timelines + @unittest.skip('skipping non-updated test') def test_get_mentions_timeline(self): """Test returning mentions timeline for authenticated user succeeds""" self.api.get_mentions_timeline() + @unittest.skip('skipping non-updated test') def test_get_user_timeline(self): """Test returning timeline for authenticated user and random user succeeds""" self.api.get_user_timeline() # Authenticated User Timeline self.api.get_user_timeline(screen_name='twitter') # Random User Timeline + @unittest.skip('skipping non-updated test') def test_get_protected_user_timeline_following(self): """Test returning a protected user timeline who you are following succeeds""" self.api.get_user_timeline(screen_name=protected_twitter_1) + @unittest.skip('skipping non-updated test') def test_get_protected_user_timeline_not_following(self): """Test returning a protected user timeline who you are not following fails and raise a TwythonAuthError""" self.assertRaises(TwythonAuthError, self.api.get_user_timeline, screen_name=protected_twitter_2) + @unittest.skip('skipping non-updated test') def test_retweeted_of_me(self): """Test that getting recent tweets by authenticated user that have been retweeted by others succeeds""" self.api.retweeted_of_me() + @unittest.skip('skipping non-updated test') def test_get_home_timeline(self): """Test returning home timeline for authenticated user succeeds""" self.api.get_home_timeline() # Tweets + @unittest.skip('skipping non-updated test') def test_get_retweets(self): """Test getting retweets of a specific tweet succeeds""" self.api.get_retweets(id=test_tweet_id) + @unittest.skip('skipping non-updated test') def test_show_status(self): """Test returning a single status details succeeds""" self.api.show_status(id=test_tweet_id) + @unittest.skip('skipping non-updated test') def test_update_and_destroy_status(self): """Test updating and deleting a status succeeds""" status = self.api.update_status(status='Test post just to get deleted :( %s' % int(time.time())) self.api.destroy_status(id=status['id_str']) + @unittest.skip('skipping non-updated test') def test_get_oembed_tweet(self): """Test getting info to embed tweet on Third Party site succeeds""" self.api.get_oembed_tweet(id='99530515043983360') + @unittest.skip('skipping non-updated test') def test_get_retweeters_ids(self): """Test getting ids for people who retweeted a tweet succeeds""" self.api.get_retweeters_ids(id='99530515043983360') # Search + @unittest.skip('skipping non-updated test') def test_search(self): """Test searching tweets succeeds""" self.api.search(q='twitter') # Direct Messages + @unittest.skip('skipping non-updated test') def test_get_direct_messages(self): """Test getting the authenticated users direct messages succeeds""" self.api.get_direct_messages() + @unittest.skip('skipping non-updated test') def test_get_sent_messages(self): """Test getting the authenticated users direct messages they've sent succeeds""" self.api.get_sent_messages() + @unittest.skip('skipping non-updated test') def test_send_get_and_destroy_direct_message(self): """Test sending, getting, then destory a direct message succeeds""" message = self.api.send_direct_message(screen_name=protected_twitter_1, @@ -108,6 +123,7 @@ def test_send_get_and_destroy_direct_message(self): self.api.get_direct_message(id=message['id_str']) self.api.destroy_direct_message(id=message['id_str']) + @unittest.skip('skipping non-updated test') def test_send_direct_message_to_non_follower(self): """Test sending a direct message to someone who doesn't follow you fails""" @@ -115,45 +131,54 @@ def test_send_direct_message_to_non_follower(self): screen_name=protected_twitter_2, text='Yo, man! %s' % int(time.time())) # Friends & Followers + @unittest.skip('skipping non-updated test') def test_get_user_ids_of_blocked_retweets(self): """Test that collection of user_ids that the authenticated user does not want to receive retweets from succeeds""" self.api.get_user_ids_of_blocked_retweets(stringify_ids=True) + @unittest.skip('skipping non-updated test') def test_get_friends_ids(self): """Test returning ids of users the authenticated user and then a random user is following succeeds""" self.api.get_friends_ids() self.api.get_friends_ids(screen_name='twitter') + @unittest.skip('skipping non-updated test') def test_get_followers_ids(self): """Test returning ids of users the authenticated user and then a random user are followed by succeeds""" self.api.get_followers_ids() self.api.get_followers_ids(screen_name='twitter') + @unittest.skip('skipping non-updated test') def test_lookup_friendships(self): """Test returning relationships of the authenticating user to the comma-separated list of up to 100 screen_names or user_ids provided succeeds""" self.api.lookup_friendships(screen_name='twitter,ryanmcgrath') + @unittest.skip('skipping non-updated test') def test_get_incoming_friendship_ids(self): """Test returning incoming friendship ids succeeds""" self.api.get_incoming_friendship_ids() + @unittest.skip('skipping non-updated test') def test_get_outgoing_friendship_ids(self): """Test returning outgoing friendship ids succeeds""" self.api.get_outgoing_friendship_ids() + @unittest.skip('skipping non-updated test') def test_create_friendship(self): """Test creating a friendship succeeds""" self.api.create_friendship(screen_name='justinbieber') + @unittest.skip('skipping non-updated test') def test_destroy_friendship(self): """Test destroying a friendship succeeds""" self.api.destroy_friendship(screen_name='justinbieber') + @unittest.skip('skipping non-updated test') def test_update_friendship(self): """Test updating friendships succeeds""" self.api.update_friendship(screen_name=protected_twitter_1, @@ -162,16 +187,19 @@ def test_update_friendship(self): self.api.update_friendship(screen_name=protected_twitter_1, retweets=False) + @unittest.skip('skipping non-updated test') def test_show_friendships(self): """Test showing specific friendship succeeds""" self.api.show_friendship(target_screen_name=protected_twitter_1) + @unittest.skip('skipping non-updated test') def test_get_friends_list(self): """Test getting list of users authenticated user then random user is following succeeds""" self.api.get_friends_list() self.api.get_friends_list(screen_name='twitter') + @unittest.skip('skipping non-updated test') def test_get_followers_list(self): """Test getting list of users authenticated user then random user are followed by succeeds""" @@ -179,117 +207,142 @@ def test_get_followers_list(self): self.api.get_followers_list(screen_name='twitter') # Users + @unittest.skip('skipping non-updated test') def test_get_account_settings(self): """Test getting the authenticated user account settings succeeds""" self.api.get_account_settings() + @unittest.skip('skipping non-updated test') def test_verify_credentials(self): """Test representation of the authenticated user call succeeds""" self.api.verify_credentials() + @unittest.skip('skipping non-updated test') def test_update_account_settings(self): """Test updating a user account settings succeeds""" self.api.update_account_settings(lang='en') + @unittest.skip('skipping non-updated test') def test_update_delivery_service(self): """Test updating delivery settings fails because we don't have a mobile number on the account""" self.assertRaises(TwythonError, self.api.update_delivery_service, device='none') + @unittest.skip('skipping non-updated test') def test_update_profile(self): """Test updating profile succeeds""" self.api.update_profile(include_entities='true') + @unittest.skip('skipping non-updated test') def test_update_profile_colors(self): """Test updating profile colors succeeds""" self.api.update_profile_colors(profile_background_color='3D3D3D') + @unittest.skip('skipping non-updated test') def test_list_blocks(self): """Test listing users who are blocked by the authenticated user succeeds""" self.api.list_blocks() + @unittest.skip('skipping non-updated test') def test_list_block_ids(self): """Test listing user ids who are blocked by the authenticated user succeeds""" self.api.list_block_ids() + @unittest.skip('skipping non-updated test') def test_create_block(self): """Test blocking a user succeeds""" self.api.create_block(screen_name='justinbieber') + @unittest.skip('skipping non-updated test') def test_destroy_block(self): """Test unblocking a user succeeds""" self.api.destroy_block(screen_name='justinbieber') + @unittest.skip('skipping non-updated test') def test_lookup_user(self): """Test listing a number of user objects succeeds""" self.api.lookup_user(screen_name='twitter,justinbieber') + @unittest.skip('skipping non-updated test') def test_show_user(self): """Test showing one user works""" self.api.show_user(screen_name='twitter') + @unittest.skip('skipping non-updated test') def test_search_users(self): """Test that searching for users succeeds""" self.api.search_users(q='Twitter API') + @unittest.skip('skipping non-updated test') def test_get_contributees(self): """Test returning list of accounts the specified user can contribute to succeeds""" self.api.get_contributees(screen_name='TechCrunch') + @unittest.skip('skipping non-updated test') def test_get_contributors(self): """Test returning list of accounts that contribute to the authenticated user fails because we are not a Contributor account""" self.assertRaises(TwythonError, self.api.get_contributors, screen_name=screen_name) + @unittest.skip('skipping non-updated test') def test_remove_profile_banner(self): """Test removing profile banner succeeds""" self.api.remove_profile_banner() + @unittest.skip('skipping non-updated test') def test_get_profile_banner_sizes(self): """Test getting list of profile banner sizes fails because we have not uploaded a profile banner""" self.assertRaises(TwythonError, self.api.get_profile_banner_sizes) # Suggested Users + @unittest.skip('skipping non-updated test') def test_get_user_suggestions_by_slug(self): """Test getting user suggestions by slug succeeds""" self.api.get_user_suggestions_by_slug(slug='twitter') + @unittest.skip('skipping non-updated test') def test_get_user_suggestions(self): """Test getting user suggestions succeeds""" self.api.get_user_suggestions() + @unittest.skip('skipping non-updated test') def test_get_user_suggestions_statuses_by_slug(self): """Test getting status of suggested users succeeds""" self.api.get_user_suggestions_statuses_by_slug(slug='funny') # Favorites + @unittest.skip('skipping non-updated test') def test_get_favorites(self): """Test getting list of favorites for the authenticated user succeeds""" self.api.get_favorites() + @unittest.skip('skipping non-updated test') def test_create_and_destroy_favorite(self): """Test creating and destroying a favorite on a tweet succeeds""" self.api.create_favorite(id=test_tweet_id) self.api.destroy_favorite(id=test_tweet_id) # Lists + @unittest.skip('skipping non-updated test') def test_show_lists(self): """Test show lists for specified user""" self.api.show_lists(screen_name='twitter') + @unittest.skip('skipping non-updated test') def test_get_list_statuses(self): """Test timeline of tweets authored by members of the specified list succeeds""" self.api.get_list_statuses(slug=test_list_slug, owner_screen_name=test_list_owner_screen_name) + @unittest.skip('skipping non-updated test') def test_create_update_destroy_list_add_remove_list_members(self): """Test create a list, adding and removing members then deleting the list succeeds""" @@ -311,15 +364,18 @@ def test_create_update_destroy_list_add_remove_list_members(self): self.api.delete_list(list_id=list_id) + @unittest.skip('skipping non-updated test') def test_get_list_memberships(self): """Test list of memberhips the authenticated user succeeds""" self.api.get_list_memberships() + @unittest.skip('skipping non-updated test') def test_get_list_subscribers(self): """Test list of subscribers of a specific list succeeds""" self.api.get_list_subscribers(slug=test_list_slug, owner_screen_name=test_list_owner_screen_name) + @unittest.skip('skipping non-updated test') def test_subscribe_is_subbed_and_unsubscribe_to_list(self): """Test subscribing, is a list sub and unsubbing to list succeeds""" self.api.subscribe_to_list(slug=test_list_slug, @@ -331,6 +387,7 @@ def test_subscribe_is_subbed_and_unsubscribe_to_list(self): self.api.unsubscribe_from_list(slug=test_list_slug, owner_screen_name=test_list_owner_screen_name) + @unittest.skip('skipping non-updated test') def test_is_list_member(self): """Test returning if specified user is member of a list succeeds""" # Returns 404 if not list member @@ -338,31 +395,37 @@ def test_is_list_member(self): owner_screen_name=test_list_owner_screen_name, screen_name='themattharris') + @unittest.skip('skipping non-updated test') def test_get_list_members(self): """Test listing members of the specified list succeeds""" self.api.get_list_members(slug=test_list_slug, owner_screen_name=test_list_owner_screen_name) + @unittest.skip('skipping non-updated test') def test_get_specific_list(self): """Test getting specific list succeeds""" self.api.get_specific_list(slug=test_list_slug, owner_screen_name=test_list_owner_screen_name) + @unittest.skip('skipping non-updated test') def test_get_list_subscriptions(self): """Test collection of the lists the specified user is subscribed to succeeds""" self.api.get_list_subscriptions(screen_name='twitter') + @unittest.skip('skipping non-updated test') def test_show_owned_lists(self): """Test collection of lists the specified user owns succeeds""" self.api.show_owned_lists(screen_name='twitter') # Saved Searches + @unittest.skip('skipping non-updated test') def test_get_saved_searches(self): """Test getting list of saved searches for authenticated user succeeds""" self.api.get_saved_searches() + @unittest.skip('skipping non-updated test') def test_create_get_destroy_saved_search(self): """Test getting list of saved searches for authenticated user succeeds""" @@ -373,57 +436,69 @@ def test_create_get_destroy_saved_search(self): self.api.destroy_saved_search(id=saved_search_id) # Places & Geo + @unittest.skip('skipping non-updated test') def test_get_geo_info(self): """Test getting info about a geo location succeeds""" self.api.get_geo_info(place_id='df51dec6f4ee2b2c') + @unittest.skip('skipping non-updated test') def test_reverse_geo_code(self): """Test reversing geocode succeeds""" self.api.reverse_geocode(lat='37.76893497', long='-122.42284884') + @unittest.skip('skipping non-updated test') def test_search_geo(self): """Test search for places that can be attached to a statuses/update succeeds""" self.api.search_geo(query='Toronto') + @unittest.skip('skipping non-updated test') def test_get_similar_places(self): """Test locates places near the given coordinates which are similar in name succeeds""" self.api.get_similar_places(lat='37', long='-122', name='Twitter HQ') # Trends + @unittest.skip('skipping non-updated test') def test_get_place_trends(self): """Test getting the top 10 trending topics for a specific WOEID succeeds""" self.api.get_place_trends(id=1) + @unittest.skip('skipping non-updated test') def test_get_available_trends(self): """Test returning locations that Twitter has trending topic information for succeeds""" self.api.get_available_trends() + @unittest.skip('skipping non-updated test') def test_get_closest_trends(self): """Test getting the locations that Twitter has trending topic information for, closest to a specified location succeeds""" self.api.get_closest_trends(lat='37', long='-122') # Help + @unittest.skip('skipping non-updated test') def test_get_twitter_configuration(self): """Test getting Twitter's configuration succeeds""" self.api.get_twitter_configuration() + @unittest.skip('skipping non-updated test') def test_get_supported_languages(self): """Test getting languages supported by Twitter succeeds""" self.api.get_supported_languages() + @unittest.skip('skipping non-updated test') def test_privacy_policy(self): """Test getting Twitter's Privacy Policy succeeds""" self.api.get_privacy_policy() + @unittest.skip('skipping non-updated test') def test_get_tos(self): """Test getting the Twitter Terms of Service succeeds""" self.api.get_tos() + @unittest.skip('skipping non-updated test') def test_get_application_rate_limit_status(self): """Test getting application rate limit status succeeds""" self.oauth2_api.get_application_rate_limit_status() diff --git a/tests/test_streaming.py b/tests/test_streaming.py index 1b8db6c4..d0cf20a9 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -29,19 +29,24 @@ def on_error(self, status_code, data): oauth_token, oauth_token_secret, client_args=client_args) + @unittest.skip('skipping non-updated test') def test_stream_status_filter(self): self.api.statuses.filter(track='twitter') + @unittest.skip('skipping non-updated test') def test_stream_status_sample(self): self.api.statuses.sample() + @unittest.skip('skipping non-updated test') def test_stream_status_firehose(self): self.assertRaises(TwythonStreamError, self.api.statuses.firehose, track='twitter') + @unittest.skip('skipping non-updated test') def test_stream_site(self): self.assertRaises(TwythonStreamError, self.api.site, follow='twitter') + @unittest.skip('skipping non-updated test') def test_stream_user(self): self.api.user(track='twitter') From 30ff4319e2325cc6aaeb8b9ad4e404e087b0cd23 Mon Sep 17 00:00:00 2001 From: cash Date: Sat, 11 Jan 2014 15:52:46 -0500 Subject: [PATCH 035/165] updated unit tests and travis config for python 2.6 and 3 --- .travis.yml | 4 +++- tests/config.py | 6 ++++++ tests/test_auth.py | 4 +--- tests/test_core.py | 18 +++++++----------- tests/test_endpoints.py | 3 +-- tests/test_streaming.py | 4 +--- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8411c027..91ebf1d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,9 @@ env: - TEST_LIST_SLUG=team - TEST_LIST_OWNER_SCREEN_NAME=twitterapi - ACCESS_TOKEN_B64=U2FsdGVkX18QdBhvMNshM4PGy04tU3HLwKP+nNSoNZHKsvGLjELcWEXN2LIu/T+yngX1vGONf9lo14ElnfS4k7sfhiru8phR4+rZuBVP3bDvC2A6fXJuhuLqNhBrWqg32WQewvxLWDWBoKmnvRHg5b74GHh+IN/12tU0cBF2HK8= -install: pip install -r requirements.txt +install: + - pip install -r requirements.txt + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi script: nosetests -v -w tests/ --logging-filter="twython" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing notifications: email: false diff --git a/tests/config.py b/tests/config.py index 4e8895ec..d26b6e1a 100644 --- a/tests/config.py +++ b/tests/config.py @@ -1,5 +1,11 @@ import os +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 6: + import unittest2 as unittest +else: + import unittest + app_key = os.environ.get('APP_KEY') app_secret = os.environ.get('APP_SECRET') oauth_token = os.environ.get('OAUTH_TOKEN') diff --git a/tests/test_auth.py b/tests/test_auth.py index 3b2a7133..7de4160c 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -1,8 +1,6 @@ from twython import Twython, TwythonError, TwythonAuthError -from .config import app_key, app_secret, screen_name - -import unittest +from .config import app_key, app_secret, screen_name, unittest class TwythonAuthTestCase(unittest.TestCase): diff --git a/tests/test_core.py b/tests/test_core.py index 17d1fceb..be229253 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,21 +1,17 @@ from twython import Twython, TwythonError, TwythonAuthError from .config import ( - test_tweet_object, test_tweet_html + test_tweet_object, test_tweet_html, unittest ) -try: - import unittest2 as unittest -except ImportError: - import unittest - import responses import requests -try: - import io.StringIO as StringIO -except ImportError: - import StringIO +from twython.compat import is_py2 +if is_py2: + from StringIO import StringIO +else: + from io import StringIO try: import unittest.mock as mock @@ -141,7 +137,7 @@ def test_request_should_handle_file_as_parameter(self): url = self.get_url(endpoint) responses.add(responses.POST, url) - mock_file = StringIO.StringIO("Twython test image") + mock_file = StringIO("Twython test image") self.api.request(endpoint, method='POST', params={'image': mock_file}) self.assertIn('filename="image"', responses.calls[0].request.body) diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index edeacfa1..6204a577 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -4,11 +4,10 @@ app_key, app_secret, oauth_token, oauth_token_secret, protected_twitter_1, protected_twitter_2, screen_name, test_tweet_id, test_list_slug, test_list_owner_screen_name, - access_token, test_tweet_object, test_tweet_html + access_token, test_tweet_object, test_tweet_html, unittest ) import time -import unittest class TwythonEndpointsTestCase(unittest.TestCase): diff --git a/tests/test_streaming.py b/tests/test_streaming.py index d0cf20a9..9db7059f 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -1,11 +1,9 @@ from twython import TwythonStreamer, TwythonStreamError from .config import ( - app_key, app_secret, oauth_token, oauth_token_secret + app_key, app_secret, oauth_token, oauth_token_secret, unittest ) -import unittest - class TwythonStreamTestCase(unittest.TestCase): def setUp(self): From 9f7d38181ed65b0bba05d205689877cd9d70f4d9 Mon Sep 17 00:00:00 2001 From: cash Date: Sat, 11 Jan 2014 18:08:52 -0500 Subject: [PATCH 036/165] python 3.3 workaround for dropbox/responses issue --- tests/test_core.py | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index be229253..ddf52566 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -27,11 +27,22 @@ def get_url(self, endpoint): """Convenience function for mapping from endpoint to URL""" return '%s/%s.json' % (self.api.api_url % self.api.api_version, endpoint) + def register_response(self, method, url, body='', match_querystring=False, + status=200, adding_headers=None, stream=False, + content_type='text/plain'): + """Temporary function to work around python 3.3 issue with responses""" + # responses uses BytesIO to hold the body so it needs to be in bytes + if not is_py2: + body = bytes(body, 'UTF-8') + + responses.add(method, url, body, match_querystring, + status, adding_headers, stream, content_type) + @responses.activate def test_request_should_handle_full_endpoint(self): """Test that request() accepts a full URL for the endpoint argument""" url = 'https://api.twitter.com/1.1/search/tweets.json' - responses.add(responses.GET, url) + self.register_response(responses.GET, url) self.api.request(url) @@ -42,7 +53,7 @@ def test_request_should_handle_full_endpoint(self): def test_request_should_handle_relative_endpoint(self): """Test that request() accepts a twitter endpoint name for the endpoint argument""" url = 'https://api.twitter.com/2.0/search/tweets.json' - responses.add(responses.GET, url) + self.register_response(responses.GET, url) self.api.request('search/tweets', version='2.0') @@ -53,7 +64,7 @@ def test_request_should_handle_relative_endpoint(self): def test_request_should_post_request_regardless_of_case(self): """Test that request() accepts the HTTP method name regardless of case""" url = 'https://api.twitter.com/1.1/statuses/update.json' - responses.add(responses.POST, url) + self.register_response(responses.POST, url) self.api.request(url, method='POST') self.api.request(url, method='post') @@ -73,7 +84,7 @@ def test_request_should_encode_boolean_as_lowercase_string(self): """Test that request() encodes a boolean parameter as a lowercase string""" endpoint = 'search/tweets' url = self.get_url(endpoint) - responses.add(responses.GET, url) + self.register_response(responses.GET, url) self.api.request(endpoint, params={'include_entities': True}) self.api.request(endpoint, params={'include_entities': False}) @@ -86,7 +97,7 @@ def test_request_should_handle_string_or_number_parameter(self): """Test that request() encodes a numeric or string parameter correctly""" endpoint = 'search/tweets' url = self.get_url(endpoint) - responses.add(responses.GET, url) + self.register_response(responses.GET, url) self.api.request(endpoint, params={'lang': 'es'}) self.api.request(endpoint, params={'count': 50}) @@ -100,7 +111,7 @@ def test_request_should_encode_list_of_strings_as_string(self): endpoint = 'search/tweets' url = self.get_url(endpoint) location = ['37.781157', '-122.39872', '1mi'] - responses.add(responses.GET, url) + self.register_response(responses.GET, url) self.api.request(endpoint, params={'geocode': location}) @@ -113,7 +124,7 @@ def test_request_should_encode_numeric_list_as_string(self): endpoint = 'search/tweets' url = self.get_url(endpoint) location = [37.781157, -122.39872, '1mi'] - responses.add(responses.GET, url) + self.register_response(responses.GET, url) self.api.request(endpoint, params={'geocode': location}) @@ -124,7 +135,7 @@ def test_request_should_ignore_bad_parameter(self): """Test that request() ignores unexpected parameter types""" endpoint = 'search/tweets' url = self.get_url(endpoint) - responses.add(responses.GET, url) + self.register_response(responses.GET, url) self.api.request(endpoint, params={'geocode': self}) @@ -135,11 +146,14 @@ def test_request_should_handle_file_as_parameter(self): """Test that request() pulls a file out of params for requests lib""" endpoint = 'account/update_profile_image' url = self.get_url(endpoint) - responses.add(responses.POST, url) + self.register_response(responses.POST, url) mock_file = StringIO("Twython test image") self.api.request(endpoint, method='POST', params={'image': mock_file}) + if not is_py2: + responses.calls[0].request.body = responses.calls[0].request.body.decode("utf-8") + self.assertIn('filename="image"', responses.calls[0].request.body) self.assertIn("Twython test image", responses.calls[0].request.body) @@ -148,10 +162,13 @@ def test_request_should_put_params_in_body_when_post(self): """Test that request() passes params as data when the request is a POST""" endpoint = 'statuses/update' url = self.get_url(endpoint) - responses.add(responses.POST, url) + self.register_response(responses.POST, url) self.api.request(endpoint, method='POST', params={'status': 'this is a test'}) + if not is_py2: + responses.calls[0].request.body = responses.calls[0].request.body.decode("utf-8") + self.assertIn('status=this+is+a+test', responses.calls[0].request.body) self.assertNotIn('status=this+is+a+test', responses.calls[0].request.url) @@ -160,7 +177,7 @@ def test_get_uses_get_method(self): """Test Twython generic GET request works""" endpoint = 'account/verify_credentials' url = self.get_url(endpoint) - responses.add(responses.GET, url) + self.register_response(responses.GET, url) self.api.get(endpoint) @@ -172,7 +189,7 @@ def test_post_uses_post_method(self): """Test Twython generic POST request works""" endpoint = 'statuses/update' url = self.get_url(endpoint) - responses.add(responses.POST, url) + self.register_response(responses.POST, url) self.api.post(endpoint, params={'status': 'I love Twython!'}) @@ -191,7 +208,7 @@ def test_get_lastfunction_header_should_return_header(self): """Test getting last specific header of the last API call works""" endpoint = 'statuses/home_timeline' url = self.get_url(endpoint) - responses.add(responses.GET, url, adding_headers={'x-rate-limit-remaining': 37}) + self.register_response(responses.GET, url, adding_headers={'x-rate-limit-remaining': 37}) self.api.get(endpoint) From f8d47c13d36cf6b945b555ca0d7cd63dc6a090fb Mon Sep 17 00:00:00 2001 From: wcdolphin Date: Sun, 12 Jan 2014 15:21:27 -0800 Subject: [PATCH 037/165] Adds retry_after attribute to TwythonRateLimitError --- twython/exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/twython/exceptions.py b/twython/exceptions.py index b9c9df57..4aa7dbab 100644 --- a/twython/exceptions.py +++ b/twython/exceptions.py @@ -53,6 +53,8 @@ def __init__(self, msg, error_code, retry_after=None): msg = '%s (Retry after %d seconds)' % (msg, retry_after) TwythonError.__init__(self, msg, error_code=error_code) + self.retry_after = retry_after + class TwythonStreamError(TwythonError): """Raised when an invalid response from the Stream API is received""" From c83304edf27514994bb66cb2d1b2056d9d1893e5 Mon Sep 17 00:00:00 2001 From: Cash Costello Date: Tue, 14 Jan 2014 11:14:14 -0500 Subject: [PATCH 038/165] requests returns content as bytes so use bytes for test strings. Also changed api verison in test 1.1 to not confuse people --- tests/test_core.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index ddf52566..caf07aeb 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -52,10 +52,10 @@ def test_request_should_handle_full_endpoint(self): @responses.activate def test_request_should_handle_relative_endpoint(self): """Test that request() accepts a twitter endpoint name for the endpoint argument""" - url = 'https://api.twitter.com/2.0/search/tweets.json' + url = 'https://api.twitter.com/1.1/search/tweets.json' self.register_response(responses.GET, url) - self.api.request('search/tweets', version='2.0') + self.api.request('search/tweets', version='1.1') self.assertEqual(1, len(responses.calls)) self.assertEqual(url, responses.calls[0].request.url) @@ -151,11 +151,8 @@ def test_request_should_handle_file_as_parameter(self): mock_file = StringIO("Twython test image") self.api.request(endpoint, method='POST', params={'image': mock_file}) - if not is_py2: - responses.calls[0].request.body = responses.calls[0].request.body.decode("utf-8") - - self.assertIn('filename="image"', responses.calls[0].request.body) - self.assertIn("Twython test image", responses.calls[0].request.body) + self.assertIn(b'filename="image"', responses.calls[0].request.body) + self.assertIn(b"Twython test image", responses.calls[0].request.body) @responses.activate def test_request_should_put_params_in_body_when_post(self): @@ -166,10 +163,7 @@ def test_request_should_put_params_in_body_when_post(self): self.api.request(endpoint, method='POST', params={'status': 'this is a test'}) - if not is_py2: - responses.calls[0].request.body = responses.calls[0].request.body.decode("utf-8") - - self.assertIn('status=this+is+a+test', responses.calls[0].request.body) + self.assertIn(b'status=this+is+a+test', responses.calls[0].request.body) self.assertNotIn('status=this+is+a+test', responses.calls[0].request.url) @responses.activate From fa2122127cd84e0acd25189cb0713dfc1e4f0919 Mon Sep 17 00:00:00 2001 From: cash Date: Sat, 25 Jan 2014 16:56:48 -0500 Subject: [PATCH 039/165] Fixes #302 simplifies json decoding and handling of errors. Adds tests. --- tests/test_core.py | 68 +++++++++++++++++++++++++++++++++++++++++++--- twython/api.py | 52 +++++++++++++++++------------------ 2 files changed, 89 insertions(+), 31 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index caf07aeb..df206608 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,4 +1,4 @@ -from twython import Twython, TwythonError, TwythonAuthError +from twython import Twython, TwythonError, TwythonAuthError, TwythonRateLimitError from .config import ( test_tweet_object, test_tweet_html, unittest @@ -27,10 +27,11 @@ def get_url(self, endpoint): """Convenience function for mapping from endpoint to URL""" return '%s/%s.json' % (self.api.api_url % self.api.api_version, endpoint) - def register_response(self, method, url, body='', match_querystring=False, + def register_response(self, method, url, body='{}', match_querystring=False, status=200, adding_headers=None, stream=False, - content_type='text/plain'): - """Temporary function to work around python 3.3 issue with responses""" + content_type='application/json; charset=utf-8'): + """Wrapper function for responses for simpler unit tests""" + # responses uses BytesIO to hold the body so it needs to be in bytes if not is_py2: body = bytes(body, 'UTF-8') @@ -197,6 +198,65 @@ def test_raise_twython_error_on_request_exception(self): get_mock.side_effect = requests.RequestException("hostname 'example.com' doesn't match ...") self.assertRaises(TwythonError, self.api.get, 'https://example.com') + @responses.activate + def test_request_should_get_convert_json_to_data(self): + """Test that Twython converts JSON data to a Python object""" + endpoint = 'statuses/show' + url = self.get_url(endpoint) + self.register_response(responses.GET, url, body='{"id": 210462857140252672}') + + data = self.api.request(endpoint, params={'id': 210462857140252672}) + + self.assertEqual({'id': 210462857140252672}, data) + + @responses.activate + def test_request_should_raise_exception_with_invalid_json(self): + """Test that Twython handles invalid JSON (though Twitter should not return it)""" + endpoint = 'statuses/show' + url = self.get_url(endpoint) + self.register_response(responses.GET, url, body='{"id: 210462857140252672}') + + self.assertRaises(TwythonError, self.api.request, endpoint, params={'id': 210462857140252672}) + + @responses.activate + def test_request_should_handle_401(self): + """Test that Twython raises an auth error on 401 error""" + endpoint = 'statuses/home_timeline' + url = self.get_url(endpoint) + self.register_response(responses.GET, url, body='{"errors":[{"message":"Error"}]}', status=401) + + self.assertRaises(TwythonAuthError, self.api.request, endpoint) + + @responses.activate + def test_request_should_handle_400_for_missing_auth_data(self): + """Test that Twython raises an auth error on 400 error when no oauth data sent""" + endpoint = 'statuses/home_timeline' + url = self.get_url(endpoint) + self.register_response(responses.GET, url, + body='{"errors":[{"message":"Bad Authentication data"}]}', status=400) + + self.assertRaises(TwythonAuthError, self.api.request, endpoint) + + @responses.activate + def test_request_should_handle_400_that_is_not_auth_related(self): + """Test that Twython raises a normal error on 400 error when unrelated to authorization""" + endpoint = 'statuses/home_timeline' + url = self.get_url(endpoint) + self.register_response(responses.GET, url, + body='{"errors":[{"message":"Bad request"}]}', status=400) + + self.assertRaises(TwythonError, self.api.request, endpoint) + + @responses.activate + def test_request_should_handle_rate_limit(self): + """Test that Twython raises an rate limit error on 429""" + endpoint = 'statuses/home_timeline' + url = self.get_url(endpoint) + self.register_response(responses.GET, url, + body='{"errors":[{"message":"Rate Limit"}]}', status=429) + + self.assertRaises(TwythonRateLimitError, self.api.request, endpoint) + @responses.activate def test_get_lastfunction_header_should_return_header(self): """Test getting last specific header of the last API call works""" diff --git a/twython/api.py b/twython/api.py index ef1aac63..c457fd73 100644 --- a/twython/api.py +++ b/twython/api.py @@ -143,7 +143,6 @@ def _request(self, url, method='GET', params=None, api_call=None): response = func(url, **requests_args) except requests.RequestException as e: raise TwythonError(str(e)) - content = response.content.decode('utf-8') # create stash for last function intel self._last_call = { @@ -153,37 +152,18 @@ def _request(self, url, method='GET', params=None, api_call=None): 'headers': response.headers, 'status_code': response.status_code, 'url': response.url, - 'content': content, + 'content': response.text, } - # Wrap the json loads in a try, and defer an error - # Twitter will return invalid json with an error code in the headers - json_error = False - try: - try: - # try to get json - content = content.json() - except AttributeError: - # if unicode detected - content = json.loads(content) - except ValueError: - json_error = True - content = {} - + # greater than 304 (not modified) is an error if response.status_code > 304: - # If there is no error message, use a default. - errors = content.get('errors', - [{'message': 'An error occurred processing your request.'}]) - if errors and isinstance(errors, list): - error_message = errors[0]['message'] - else: - error_message = errors # pragma: no cover + error_message = self._get_error_message(response) self._last_call['api_error'] = error_message ExceptionType = TwythonError if response.status_code == 429: # Twitter API 1.1, always return 429 when rate limit is exceeded - ExceptionType = TwythonRateLimitError # pragma: no cover + ExceptionType = TwythonRateLimitError elif response.status_code == 401 or 'Bad Authentication data' in error_message: # Twitter API 1.1, returns a 401 Unauthorized or # a 400 "Bad Authentication data" for invalid/expired app keys/user tokens @@ -193,12 +173,30 @@ def _request(self, url, method='GET', params=None, api_call=None): error_code=response.status_code, retry_after=response.headers.get('retry-after')) - # if we have a json error here, then it's not an official Twitter API error - if json_error and not response.status_code in (200, 201, 202): # pragma: no cover - raise TwythonError('Response was not valid JSON, unable to decode.') + try: + content = response.json() + except json.JSONDecodeError: + raise TwythonError('Response was not valid JSON. Unable to decode.') return content + def _get_error_message(self, response): + """Parse and return the first error message""" + + error_message = 'An error occurred processing your request.' + try: + content = response.json() + # {"errors":[{"code":34,"message":"Sorry, that page does not exist"}]} + error_message = content['errors'][0]['message'] + except json.JSONDecodeError: + # bad json data from Twitter for an error + pass + except (KeyError, IndexError): + # missing data so fallback to default message + pass + + return error_message + def request(self, endpoint, method='GET', params=None, version='1.1'): """Return dict of response received from Twitter's API From 1e627f9fb1cd0bc06704739e4426f6d1218352ac Mon Sep 17 00:00:00 2001 From: cash Date: Sat, 25 Jan 2014 17:15:17 -0500 Subject: [PATCH 040/165] need to use ValueError to account for different json libraries throwing different exceptions --- twython/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twython/api.py b/twython/api.py index c457fd73..db793811 100644 --- a/twython/api.py +++ b/twython/api.py @@ -175,7 +175,7 @@ def _request(self, url, method='GET', params=None, api_call=None): try: content = response.json() - except json.JSONDecodeError: + except ValueError: raise TwythonError('Response was not valid JSON. Unable to decode.') return content @@ -188,7 +188,7 @@ def _get_error_message(self, response): content = response.json() # {"errors":[{"code":34,"message":"Sorry, that page does not exist"}]} error_message = content['errors'][0]['message'] - except json.JSONDecodeError: + except ValueError: # bad json data from Twitter for an error pass except (KeyError, IndexError): From e1ee67192e37df7ff44337c9aaf8981ae0d93549 Mon Sep 17 00:00:00 2001 From: Ian Date: Fri, 21 Feb 2014 10:29:10 +1100 Subject: [PATCH 041/165] fixed paramater naming as per PEP8 --- twython/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/twython/api.py b/twython/api.py index 7eee2f41..bb272d83 100644 --- a/twython/api.py +++ b/twython/api.py @@ -386,7 +386,7 @@ def search_gen(self, search_query, **params): # pragma: no cover ) return self.cursor(self.search, q=search_query, **params) - def cursor(self, function, returnPages = False, **params): + def cursor(self, function, return_pages=False, **params): """Returns a generator for results that match a specified query. :param function: Instance of a Twython function (Twython.get_home_timeline, Twython.search) @@ -416,7 +416,7 @@ def cursor(self, function, returnPages = False, **params): else: results = content - if returnPages: + if return_pages: yield results else: for result in results: @@ -439,7 +439,7 @@ def cursor(self, function, returnPages = False, **params): except (TypeError, ValueError): # pragma: no cover raise TwythonError('Unable to generate next page of search results, `page` is not a number.') - for result in self.cursor(function, returnPages = returnPages, **params): + for result in self.cursor(function, return_pages=return_pages, **params): yield result @staticmethod From 673336ff8f8a9a647fc8e44cb05f6c8a0ec39c98 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sun, 23 Feb 2014 14:57:03 +0000 Subject: [PATCH 042/165] Failing test for Accept-Encoding header --- tests/test_core.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index df206608..4870688b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -277,6 +277,17 @@ def test_get_lastfunction_header_should_raise_error_when_no_previous_call(self): """Test attempting to get a header when no API call was made raises a TwythonError""" self.assertRaises(TwythonError, self.api.get_lastfunction_header, 'no-api-call-was-made') + @responses.activate + def test_sends_correct_accept_encoding_header(self): + """Test that Twython accepts compressed data.""" + endpoint = 'statuses/home_timeline' + url = self.get_url(endpoint) + self.register_response(responses.GET, url) + + self.api.get(endpoint) + + self.assertEqual('gzip, deflate, compress', responses.calls[0].request.headers['Accept-Encoding']) + # Static methods def test_construct_api_url(self): """Test constructing a Twitter API url works as we expect""" From 31ff35349cc6a79bd4385cce26b76578ac41239f Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sun, 23 Feb 2014 15:06:44 +0000 Subject: [PATCH 043/165] Merge headers, don't overwrite. --- twython/api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/twython/api.py b/twython/api.py index db793811..88b6735e 100644 --- a/twython/api.py +++ b/twython/api.py @@ -109,10 +109,14 @@ def __init__(self, app_key=None, app_secret=None, oauth_token=None, # Never be used again. client_args_copy = self.client_args.copy() for k, v in client_args_copy.items(): - if k in ('cert', 'headers', 'hooks', 'max_redirects', 'proxies'): + if k in ('cert', 'hooks', 'max_redirects', 'proxies'): setattr(self.client, k, v) self.client_args.pop(k) # Pop, pop! + # Headers are always present, so we unconditionally pop them and merge + # them into the session headers. + self.client.headers.update(self.client_args.pop('headers')) + self._last_call = None def __repr__(self): From f514816c6f9905da9e76d598cdbeb8f32a8ca63b Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sun, 23 Feb 2014 15:12:44 +0000 Subject: [PATCH 044/165] Fixup python3 test failure. --- tests/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index 4870688b..5b8a3749 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -286,7 +286,7 @@ def test_sends_correct_accept_encoding_header(self): self.api.get(endpoint) - self.assertEqual('gzip, deflate, compress', responses.calls[0].request.headers['Accept-Encoding']) + self.assertEqual(b'gzip, deflate, compress', responses.calls[0].request.headers['Accept-Encoding']) # Static methods def test_construct_api_url(self): From 8fd80a0ce8b312cefee5e252d4864336bc4a81e5 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Fri, 16 May 2014 17:33:09 +1000 Subject: [PATCH 045/165] Create follow_user.py Basic CLI script with example for following user. --- examples/follow_user.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/follow_user.py diff --git a/examples/follow_user.py b/examples/follow_user.py new file mode 100644 index 00000000..b9e78a88 --- /dev/null +++ b/examples/follow_user.py @@ -0,0 +1,20 @@ +from twython import Twython, TwythonError + +# Optionally accept user data from the command line (or elsewhere). +# +# Usage: follow_user.py ryanmcgrath + +import sys + +if len(sys.argv) >= 2: + target = sys.argv[1] +else: + target = raw_input("User to follow: ") # For Python 3.x use: target = input("User to follow: ") + +# Requires Authentication as of Twitter API v1.1 +twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) + +try: + twitter.create_friendship(screen_name=target, follow="true") +except TwythonError as e: + print(e) From 8af56ee790f50a1d4ccb11f055e6ffb1dea61d51 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Sat, 17 May 2014 00:26:35 +1000 Subject: [PATCH 046/165] Create unfollow_user.py CLI script to stop following a user as specified by command arguments. --- examples/unfollow_user.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/unfollow_user.py diff --git a/examples/unfollow_user.py b/examples/unfollow_user.py new file mode 100644 index 00000000..2417035e --- /dev/null +++ b/examples/unfollow_user.py @@ -0,0 +1,20 @@ +from twython import Twython, TwythonError + +# Optionally accept user data from the command line (or elsewhere). +# +# Usage: follow_user.py ryanmcgrath + +import sys + +if len(sys.argv) >= 2: + target = sys.argv[1] +else: + target = raw_input("User to follow: ") # For Python 3.x use: target = input("User to follow: ") + +# Requires Authentication as of Twitter API v1.1 +twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) + +try: + twitter.destroy_friendship(screen_name=target, follow="true") +except TwythonError as e: + print(e) From 687ccefc54eb4f59525cdfc4f2224ee7ec3c4f5f Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Sat, 17 May 2014 00:45:04 +1000 Subject: [PATCH 047/165] Create block_user.py CLI script to block a user as specified by command arguments. --- examples/block_user.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/block_user.py diff --git a/examples/block_user.py b/examples/block_user.py new file mode 100644 index 00000000..a06c5f08 --- /dev/null +++ b/examples/block_user.py @@ -0,0 +1,20 @@ +from twython import Twython, TwythonError + +# Optionally accept user data from the command line (or elsewhere). +# +# Usage: follow_user.py ryanmcgrath + +import sys + +if len(sys.argv) >= 2: + target = sys.argv[1] +else: + target = raw_input("User to follow: ") # For Python 3.x use: target = input("User to follow: ") + +# Requires Authentication as of Twitter API v1.1 +twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) + +try: + twitter.create_block(screen_name=target, follow="true") +except TwythonError as e: + print(e) From eacecd4946ce079e178ec48b74865714b0f16aa1 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Sat, 17 May 2014 01:18:35 +1000 Subject: [PATCH 048/165] Create unblock_user.py CLI script to unblock a user as specified by command arguments. --- examples/unblock_user.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/unblock_user.py diff --git a/examples/unblock_user.py b/examples/unblock_user.py new file mode 100644 index 00000000..80ea19ca --- /dev/null +++ b/examples/unblock_user.py @@ -0,0 +1,20 @@ +from twython import Twython, TwythonError + +# Optionally accept user data from the command line (or elsewhere). +# +# Usage: follow_user.py ryanmcgrath + +import sys + +if len(sys.argv) >= 2: + target = sys.argv[1] +else: + target = raw_input("User to follow: ") # For Python 3.x use: target = input("User to follow: ") + +# Requires Authentication as of Twitter API v1.1 +twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) + +try: + twitter.destroy_block(screen_name=target, follow="true") +except TwythonError as e: + print(e) From ad6cbd0a893c3aa3aca588bc1909d514f8553b7e Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Sat, 17 May 2014 01:41:53 +1000 Subject: [PATCH 049/165] Create block_spammer.py CLI script to block and report a user for spamming, as specified by command arguments. --- examples/block_spammer.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/block_spammer.py diff --git a/examples/block_spammer.py b/examples/block_spammer.py new file mode 100644 index 00000000..c579904c --- /dev/null +++ b/examples/block_spammer.py @@ -0,0 +1,20 @@ +from twython import Twython, TwythonError + +# Optionally accept user data from the command line (or elsewhere). +# +# Usage: follow_user.py ryanmcgrath + +import sys + +if len(sys.argv) >= 2: + target = sys.argv[1] +else: + target = raw_input("User to follow: ") # For Python 3.x use: target = input("User to follow: ") + +# Requires Authentication as of Twitter API v1.1 +twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) + +try: + twitter.report_spam(screen_name=target, follow="true") +except TwythonError as e: + print(e) From b29ce05bc84132e6fd8cc0fdd27387ef3c88db29 Mon Sep 17 00:00:00 2001 From: bsbkeven Date: Wed, 21 May 2014 11:18:59 -0700 Subject: [PATCH 050/165] Update endpoints.py Added endpoint for statuses/lookup (https://dev.twitter.com/docs/api/1.1/get/statuses/lookup). --- twython/endpoints.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/twython/endpoints.py b/twython/endpoints.py index 460b3e95..801ed5a0 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -73,6 +73,16 @@ def show_status(self, **params): """ return self.get('statuses/show/%s' % params.get('id'), params=params) + def lookup_status(self, **params): + """Returns fully-hydrated tweet objects for up to 100 tweets per + request, as specified by comma-separated values passed to the id + parameter. + + Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/lookup + + """ + return self.post('statuses/lookup', params=params) + def destroy_status(self, **params): """Destroys the status specified by the required ID parameter From 1455d8769ab3a20ede947ae25c6f0cc4ea21f8c3 Mon Sep 17 00:00:00 2001 From: Joe Cabrera Date: Thu, 24 Jul 2014 04:24:51 +0000 Subject: [PATCH 051/165] first pep8 work --- twython/__init__.py | 5 +- twython/advisory.py | 5 +- twython/endpoints.py | 126 +++++++++++++++++++++++++++++-------------- twython/helpers.py | 2 +- 4 files changed, 92 insertions(+), 46 deletions(-) diff --git a/twython/__init__.py b/twython/__init__.py index ab4df37f..e0920a65 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -11,8 +11,9 @@ Twython is a library for Python that wraps the Twitter API. -It aims to abstract away all the API endpoints, so that additions to the library -and/or the Twitter API won't cause any overall problems. +It aims to abstract away all the API endpoints, so that +additions to the library and/or the Twitter API won't +cause any overall problems. Questions, comments? ryan@venodesigns.net """ diff --git a/twython/advisory.py b/twython/advisory.py index f553f314..31657ee6 100644 --- a/twython/advisory.py +++ b/twython/advisory.py @@ -15,7 +15,8 @@ class TwythonDeprecationWarning(DeprecationWarning): - """Custom DeprecationWarning to be raised when methods/variables are being deprecated in Twython. - Python 2.7 > ignores DeprecationWarning so we want to specifcally bubble up ONLY Twython Deprecation Warnings + """Custom DeprecationWarning to be raised when methods/variables + are being deprecated in Twython. Python 2.7 > ignores DeprecationWarning + so we want to specifcally bubble up ONLY Twython Deprecation Warnings """ pass diff --git a/twython/endpoints.py b/twython/endpoints.py index 801ed5a0..d71fe2af 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -5,7 +5,8 @@ ~~~~~~~~~~~~~~~~~ This module provides a mixin for a :class:`Twython ` instance. -Parameters that need to be embedded in the API url just need to be passed as a keyword argument. +Parameters that need to be embedded in the API url just need to be passed +as a keyword argument. e.g. Twython.retweet(id=12345) @@ -20,7 +21,8 @@ def get_mentions_timeline(self, **params): """Returns the 20 most recent mentions (tweets containing a users's @screen_name) for the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline + Docs: + https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline """ return self.get('statuses/mentions_timeline', params=params) @@ -63,7 +65,8 @@ def get_retweets(self, **params): Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweets/%3Aid """ - return self.get('statuses/retweets/%s' % params.get('id'), params=params) + return self.get('statuses/retweets/%s' % params.get('id'), + params=params) def show_status(self, **params): """Returns a single Tweet, specified by the id parameter @@ -111,7 +114,8 @@ def update_status_with_media(self, **params): # pragma: no cover """Updates the authenticating user's current status and attaches media for upload. In other words, it creates a Tweet with a picture attached. - Docs: https://dev.twitter.com/docs/api/1.1/post/statuses/update_with_media + Docs: + https://dev.twitter.com/docs/api/1.1/post/statuses/update_with_media """ return self.post('statuses/update_with_media', params=params) @@ -184,7 +188,8 @@ def destroy_direct_message(self, **params): return self.post('direct_messages/destroy', params=params) def send_direct_message(self, **params): - """Sends a new direct message to the specified user from the authenticating user. + """Sends a new direct message to the specified user from the + authenticating user. Docs: https://dev.twitter.com/docs/api/1.1/post/direct_messages/new @@ -196,7 +201,8 @@ def get_user_ids_of_blocked_retweets(self, **params): """Returns a collection of user_ids that the currently authenticated user does not want to receive retweets from. - Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/no_retweets/ids + Docs: + https://dev.twitter.com/docs/api/1.1/get/friendships/no_retweets/ids """ return self.get('friendships/no_retweets/ids', params=params) @@ -327,7 +333,8 @@ def verify_credentials(self, **params): requesting user if authentication was successful; returns a 401 status code and an error message if not. - Docs: https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials + Docs: + https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials """ return self.get('account/verify_credentials', params=params) @@ -343,13 +350,15 @@ def update_account_settings(self, **params): def update_delivery_service(self, **params): """Sets which device Twitter delivers updates to for the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_delivery_device + Docs: + https://dev.twitter.com/docs/api/1.1/post/account/update_delivery_device """ return self.post('account/update_delivery_device', params=params) def update_profile(self, **params): - """Sets values that users are able to set under the "Account" tab of their settings page. + """Sets values that users are able to set under the "Account" tab of their + settings page. Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile @@ -359,7 +368,8 @@ def update_profile(self, **params): def update_profile_banner_image(self, **params): # pragma: no cover """Updates the authenticating user's profile background image. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_background_image + Docs: + https://dev.twitter.com/docs/api/1.1/post/account/update_profile_background_image """ return self.post('account/update_profile_banner', params=params) @@ -368,7 +378,8 @@ def update_profile_colors(self, **params): """Sets one or more hex values that control the color scheme of the authenticating user's profile page on twitter.com. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_colors + Docs: + https://dev.twitter.com/docs/api/1.1/post/account/update_profile_colors """ return self.post('account/update_profile_colors', params=params) @@ -376,13 +387,15 @@ def update_profile_colors(self, **params): def update_profile_image(self, **params): # pragma: no cover """Updates the authenticating user's profile image. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_image + Docs: + https://dev.twitter.com/docs/api/1.1/post/account/update_profile_image """ return self.post('account/update_profile_image', params=params) def list_blocks(self, **params): - """Returns a collection of user objects that the authenticating user is blocking. + """Returns a collection of user objects that the authenticating user + is blocking. Docs: https://dev.twitter.com/docs/api/1.1/get/blocks/list @@ -410,7 +423,8 @@ def create_block(self, **params): return self.post('blocks/create', params=params) def destroy_block(self, **params): - """Un-blocks the user specified in the ID parameter for the authenticating user. + """Un-blocks the user specified in the ID parameter for the + authenticating user. Docs: https://dev.twitter.com/docs/api/1.1/post/blocks/destroy @@ -419,7 +433,8 @@ def destroy_block(self, **params): def lookup_user(self, **params): """Returns fully-hydrated user objects for up to 100 users per request, - as specified by comma-separated values passed to the user_id and/or screen_name parameters. + as specified by comma-separated values passed to the user_id and/or + screen_name parameters. Docs: https://dev.twitter.com/docs/api/1.1/get/users/lookup @@ -436,7 +451,8 @@ def show_user(self, **params): return self.get('users/show', params=params) def search_users(self, **params): - """Provides a simple, relevance-based search interface to public user accounts on Twitter. + """Provides a simple, relevance-based search interface to public user + accounts on Twitter. Docs: https://dev.twitter.com/docs/api/1.1/get/users/search @@ -463,7 +479,8 @@ def remove_profile_banner(self, **params): """Removes the uploaded profile banner for the authenticating user. Returns HTTP 200 upon success. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/remove_profile_banner + Docs: + https://dev.twitter.com/docs/api/1.1/post/account/remove_profile_banner """ return self.post('account/remove_profile_banner', params=params) @@ -471,13 +488,16 @@ def remove_profile_banner(self, **params): def update_profile_background_image(self, **params): """Uploads a profile banner on behalf of the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_banner + Docs: + https://dev.twitter.com/docs/api/1.1/post/account/update_profile_banner """ - return self.post('account/update_profile_background_image', params=params) + return self.post('account/update_profile_background_image', + params=params) def get_profile_banner_sizes(self, **params): - """Returns a map of the available size variations of the specified user's profile banner. + """Returns a map of the available size variations of the specified + user's profile banner. Docs: https://dev.twitter.com/docs/api/1.1/get/users/profile_banner @@ -488,10 +508,12 @@ def get_profile_banner_sizes(self, **params): def get_user_suggestions_by_slug(self, **params): """Access the users in a given category of the Twitter suggested user list. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug + Docs: + https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug """ - return self.get('users/suggestions/%s' % params.get('slug'), params=params) + return self.get('users/suggestions/%s' % params.get('slug'), + params=params) def get_user_suggestions(self, **params): """Access to Twitter's suggested user list. @@ -503,16 +525,20 @@ def get_user_suggestions(self, **params): def get_user_suggestions_statuses_by_slug(self, **params): """Access the users in a given category of the Twitter suggested user - list and return their most recent status if they are not a protected user. + list and return their most recent status if they are not a protected + user. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug/members + Docs: + https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug/members """ - return self.get('users/suggestions/%s/members' % params.get('slug'), params=params) + return self.get('users/suggestions/%s/members' % params.get('slug'), + params=params) # Favorites def get_favorites(self, **params): - """Returns the 20 most recent Tweets favorited by the authenticating or specified user. + """Returns the 20 most recent Tweets favorited by the authenticating + or specified user. Docs: https://dev.twitter.com/docs/api/1.1/get/favorites/list @@ -521,7 +547,8 @@ def get_favorites(self, **params): get_favorites.iter_mode = 'id' def destroy_favorite(self, **params): - """Un-favorites the status specified in the ID parameter as the authenticating user. + """Un-favorites the status specified in the ID parameter as the + authenticating user. Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/destroy @@ -529,7 +556,8 @@ def destroy_favorite(self, **params): return self.post('favorites/destroy', params=params) def create_favorite(self, **params): - """Favorites the status specified in the ID parameter as the authenticating user. + """Favorites the status specified in the ID parameter as the + authenticating user. Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/create @@ -538,7 +566,8 @@ def create_favorite(self, **params): # Lists def show_lists(self, **params): - """Returns all lists the authenticating or specified user subscribes to, including their own. + """Returns all lists the authenticating or specified user subscribes to, + including their own. Docs: https://dev.twitter.com/docs/api/1.1/get/lists/list @@ -585,7 +614,8 @@ def get_list_subscribers(self, **params): def subscribe_to_list(self, **params): """Subscribes the authenticated user to the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/create + Docs: + https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/create """ return self.post('lists/subscribers/create', params=params) @@ -601,7 +631,8 @@ def is_list_subscriber(self, **params): def unsubscribe_from_list(self, **params): """Unsubscribes the authenticated user from the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/destroy + Docs: + https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/destroy """ return self.post('lists/subscribers/destroy', params=params) @@ -610,7 +641,8 @@ def create_list_members(self, **params): """Adds multiple members to a list, by specifying a comma-separated list of member ids or screen names. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all + Docs: + https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all """ return self.post('lists/members/create_all', params=params) @@ -687,7 +719,8 @@ def delete_list_members(self, **params): """Removes multiple members from a list, by specifying a comma-separated list of member ids or screen names. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all + Docs: + https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all """ return self.post('lists/members/destroy_all', params=params) @@ -714,10 +747,12 @@ def get_saved_searches(self, **params): def show_saved_search(self, **params): """Retrieve the information for the saved search represented by the given id. - Docs: https://dev.twitter.com/docs/api/1.1/get/saved_searches/show/%3Aid + Docs: + https://dev.twitter.com/docs/api/1.1/get/saved_searches/show/%3Aid """ - return self.get('saved_searches/show/%s' % params.get('id'), params=params) + return self.get('saved_searches/show/%s' % params.get('id'), + params=params) def create_saved_search(self, **params): """Create a new saved search for the authenticated user. @@ -730,10 +765,12 @@ def create_saved_search(self, **params): def destroy_saved_search(self, **params): """Destroys a saved search for the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/saved_searches/destroy/%3Aid + Docs: + https://dev.twitter.com/docs/api/1.1/post/saved_searches/destroy/%3Aid """ - return self.post('saved_searches/destroy/%s' % params.get('id'), params=params) + return self.post('saved_searches/destroy/%s' % params.get('id'), + params=params) # Places & Geo def get_geo_info(self, **params): @@ -861,7 +898,8 @@ def get_application_rate_limit_status(self, **params): """Returns the current rate limits for methods belonging to the specified resource families. - Docs: https://dev.twitter.com/docs/api/1.1/get/application/rate_limit_status + Docs: + https://dev.twitter.com/docs/api/1.1/get/application/rate_limit_status """ return self.get('application/rate_limit_status', params=params) @@ -871,9 +909,15 @@ def get_application_rate_limit_status(self, **params): TWITTER_HTTP_STATUS_CODE = { 200: ('OK', 'Success!'), 304: ('Not Modified', 'There was no new data to return.'), - 400: ('Bad Request', 'The request was invalid. An accompanying error message will explain why. This is the status code will be returned during rate limiting.'), - 401: ('Unauthorized', 'Authentication credentials were missing or incorrect.'), - 403: ('Forbidden', 'The request is understood, but it has been refused. An accompanying error message will explain why. This code is used when requests are being denied due to update limits.'), + 400: ('Bad Request', 'The request was invalid. An accompanying \ + error message will explain why. This is the status code \ + will be returned during rate limiting.'), + 401: ('Unauthorized', 'Authentication credentials were missing \ + or incorrect.'), + 403: ('Forbidden', 'The request is understood, but it has been \ + refused. An accompanying error message will explain why. \ + This code is used when requests are being denied due to \ + update limits.'), 404: ('Not Found', 'The URI requested is invalid or the resource requested, such as a user, does not exists.'), 406: ('Not Acceptable', 'Returned by the Search API when an invalid format is specified in the request.'), 410: ('Gone', 'This resource is gone. Used to indicate that an API endpoint has been turned off.'), diff --git a/twython/helpers.py b/twython/helpers.py index a4826105..f44d0ce7 100644 --- a/twython/helpers.py +++ b/twython/helpers.py @@ -28,7 +28,7 @@ def _transparent_params(_params): try: params[k] = ','.join(v) except TypeError: - params[k] = ','.join(map(str,v)) + params[k] = ','.join(map(str, v)) else: continue # pragma: no cover return params, files From 6206c0b73f33f1a900b5a4e04c4eb0744beaea77 Mon Sep 17 00:00:00 2001 From: Joe Cabrera Date: Thu, 24 Jul 2014 13:50:36 +0000 Subject: [PATCH 052/165] more pep8 fixes --- twython/api.py | 71 ++++++++++++++++++++++++++++---------------- twython/endpoints.py | 26 +++++++++++----- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/twython/api.py b/twython/api.py index 1d23a350..9ae69a15 100644 --- a/twython/api.py +++ b/twython/api.py @@ -27,29 +27,41 @@ class Twython(EndpointsMixin, object): def __init__(self, app_key=None, app_secret=None, oauth_token=None, - oauth_token_secret=None, access_token=None, token_type='bearer', - oauth_version=1, api_version='1.1', client_args=None, auth_endpoint='authenticate'): - """Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). + oauth_token_secret=None, access_token=None, + token_type='bearer', oauth_version=1, api_version='1.1', + client_args=None, auth_endpoint='authenticate'): + """Instantiates an instance of Twython. Takes optional parameters for + authentication and such (see below). :param app_key: (optional) Your applications key :param app_secret: (optional) Your applications secret key - :param oauth_token: (optional) When using **OAuth 1**, combined with oauth_token_secret to make authenticated calls - :param oauth_token_secret: (optional) When using **OAuth 1** combined with oauth_token to make authenticated calls - :param access_token: (optional) When using **OAuth 2**, provide a valid access token if you have one - :param token_type: (optional) When using **OAuth 2**, provide your token type. Default: bearer - :param oauth_version: (optional) Choose which OAuth version to use. Default: 1 - :param api_version: (optional) Choose which Twitter API version to use. Default: 1.1 - - :param client_args: (optional) Accepts some requests Session parameters and some requests Request parameters. - See http://docs.python-requests.org/en/latest/api/#sessionapi and requests section below it for details. - [ex. headers, proxies, verify(SSL verification)] - :param auth_endpoint: (optional) Lets you select which authentication endpoint will use your application. - This will allow the application to have DM access if the endpoint is 'authorize'. - Default: authenticate. - + :param oauth_token: (optional) When using **OAuth 1**, combined with + oauth_token_secret to make authenticated calls + :param oauth_token_secret: (optional) When using **OAuth 1** combined + with oauth_token to make authenticated calls + :param access_token: (optional) When using **OAuth 2**, provide a + valid access token if you have one + :param token_type: (optional) When using **OAuth 2**, provide your + token type. Default: bearer + :param oauth_version: (optional) Choose which OAuth version to use. + Default: 1 + :param api_version: (optional) Choose which Twitter API version to + use. Default: 1.1 + + :param client_args: (optional) Accepts some requests Session parameters + and some requests Request parameters. + See http://docs.python-requests.org/en/latest/api/#sessionapi + and requests section below it for details. + [ex. headers, proxies, verify(SSL verification)] + :param auth_endpoint: (optional) Lets you select which authentication + endpoint will use your application. + This will allow the application to have DM access + if the endpoint is 'authorize'. + Default: authenticate. """ - # API urls, OAuth urls and API version; needed for hitting that there API. + # API urls, OAuth urls and API version; needed for hitting that there + # API. self.api_version = api_version self.api_url = 'https://api.twitter.com/%s' @@ -75,16 +87,18 @@ def __init__(self, app_key=None, app_secret=None, oauth_token=None, self.client_args = client_args or {} default_headers = {'User-Agent': 'Twython v' + __version__} - if not 'headers' in self.client_args: + if 'headers' not in self.client_args: # If they didn't set any headers, set our defaults for them self.client_args['headers'] = default_headers elif 'User-Agent' not in self.client_args['headers']: - # If they set headers, but didn't include User-Agent.. set it for them + # If they set headers, but didn't include User-Agent.. set + # it for them self.client_args['headers'].update(default_headers) # Generate OAuth authentication object for the request # If no keys/tokens are passed to __init__, auth=None allows for - # unauthenticated requests, although I think all v1.1 requests need auth + # unauthenticated requests, although I think all v1.1 requests + # need auth auth = None if oauth_version == 1: # User Authentication is through OAuth 1 @@ -93,12 +107,14 @@ def __init__(self, app_key=None, app_secret=None, oauth_token=None, auth = OAuth1(self.app_key, self.app_secret) if self.app_key is not None and self.app_secret is not None and \ - self.oauth_token is not None and self.oauth_token_secret is not None: + self.oauth_token is not None and self.oauth_token_secret is \ + not None: auth = OAuth1(self.app_key, self.app_secret, self.oauth_token, self.oauth_token_secret) elif oauth_version == 2 and self.access_token: # Application Authentication is through OAuth 2 - token = {'token_type': token_type, 'access_token': self.access_token} + token = {'token_type': token_type, + 'access_token': self.access_token} auth = OAuth2(self.app_key, token=token) self.client = requests.Session() @@ -166,11 +182,14 @@ def _request(self, url, method='GET', params=None, api_call=None): ExceptionType = TwythonError if response.status_code == 429: - # Twitter API 1.1, always return 429 when rate limit is exceeded + # Twitter API 1.1, always return 429 when + # rate limit is exceeded ExceptionType = TwythonRateLimitError - elif response.status_code == 401 or 'Bad Authentication data' in error_message: + elif response.status_code == 401 or 'Bad Authentication data' \ + in error_message: # Twitter API 1.1, returns a 401 Unauthorized or - # a 400 "Bad Authentication data" for invalid/expired app keys/user tokens + # a 400 "Bad Authentication data" for invalid/expired + # app keys/user tokens ExceptionType = TwythonAuthError raise ExceptionType(error_message, diff --git a/twython/endpoints.py b/twython/endpoints.py index d71fe2af..bafdd7f6 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -918,13 +918,23 @@ def get_application_rate_limit_status(self, **params): refused. An accompanying error message will explain why. \ This code is used when requests are being denied due to \ update limits.'), - 404: ('Not Found', 'The URI requested is invalid or the resource requested, such as a user, does not exists.'), - 406: ('Not Acceptable', 'Returned by the Search API when an invalid format is specified in the request.'), - 410: ('Gone', 'This resource is gone. Used to indicate that an API endpoint has been turned off.'), - 422: ('Unprocessable Entity', 'Returned when an image uploaded to POST account/update_profile_banner is unable to be processed.'), - 429: ('Too Many Requests', 'Returned in API v1.1 when a request cannot be served due to the application\'s rate limit having been exhausted for the resource.'), - 500: ('Internal Server Error', 'Something is broken. Please post to the group so the Twitter team can investigate.'), + 404: ('Not Found', 'The URI requested is invalid or the resource \ + requested, such as a user, does not exists.'), + 406: ('Not Acceptable', 'Returned by the Search API when an \ + invalid format is specified in the request.'), + 410: ('Gone', 'This resource is gone. Used to indicate that an \ + API endpoint has been turned off.'), + 422: ('Unprocessable Entity', 'Returned when an image uploaded to \ + POST account/update_profile_banner is unable to be processed.'), + 429: ('Too Many Requests', 'Returned in API v1.1 when a request cannot \ + be served due to the application\'s rate limit having been \ + exhausted for the resource.'), + 500: ('Internal Server Error', 'Something is broken. Please post to the \ + group so the Twitter team can investigate.'), 502: ('Bad Gateway', 'Twitter is down or being upgraded.'), - 503: ('Service Unavailable', 'The Twitter servers are up, but overloaded with requests. Try again later.'), - 504: ('Gateway Timeout', 'The Twitter servers are up, but the request couldn\'t be serviced due to some failure within our stack. Try again later.'), + 503: ('Service Unavailable', 'The Twitter servers are up, but overloaded \ + with requests. Try again later.'), + 504: ('Gateway Timeout', 'The Twitter servers are up, but the request \ + couldn\'t be serviced due to some failure within our stack. Try \ + again later.'), } From a0fec2f004b0260dd85cd012d471e441403c4887 Mon Sep 17 00:00:00 2001 From: Joe Cabrera Date: Sat, 26 Jul 2014 11:48:36 -0400 Subject: [PATCH 053/165] pep8 finished --- examples/follow_user.py | 3 +- examples/search_results.py | 4 +- examples/stream.py | 6 +- setup.py | 5 +- tests/config.py | 3 +- tests/test_core.py | 12 +-- tests/test_endpoints.py | 22 ++++-- twython/api.py | 145 +++++++++++++++++++++++++------------ twython/streaming/api.py | 33 ++++++--- 9 files changed, 156 insertions(+), 77 deletions(-) diff --git a/examples/follow_user.py b/examples/follow_user.py index b9e78a88..21db370d 100644 --- a/examples/follow_user.py +++ b/examples/follow_user.py @@ -9,7 +9,8 @@ if len(sys.argv) >= 2: target = sys.argv[1] else: - target = raw_input("User to follow: ") # For Python 3.x use: target = input("User to follow: ") + target = raw_input("User to follow: ") + # For Python 3.x use: target = input("User to follow: ") # Requires Authentication as of Twitter API v1.1 twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) diff --git a/examples/search_results.py b/examples/search_results.py index 99841396..3eff41d3 100644 --- a/examples/search_results.py +++ b/examples/search_results.py @@ -8,5 +8,7 @@ print e for tweet in search_results['statuses']: - print 'Tweet from @%s Date: %s' % (tweet['user']['screen_name'].encode('utf-8'), tweet['created_at']) + print 'Tweet from @%s Date: %s' % (tweet['user']['screen_nam\ + e'].encode('utf-8'), + tweet['created_at']) print tweet['text'].encode('utf-8'), '\n' diff --git a/examples/stream.py b/examples/stream.py index 0ff5c048..1d1ee1ff 100644 --- a/examples/stream.py +++ b/examples/stream.py @@ -16,5 +16,7 @@ def on_error(self, status_code, data): OAUTH_TOKEN, OAUTH_TOKEN_SECRET) stream.statuses.filter(track='twitter') -#stream.user() # Read the authenticated users home timeline (what they see on Twitter) in real-time -#stream.site(follow='twitter') +# stream.user() +# Read the authenticated users home timeline +# (what they see on Twitter) in real-time +# stream.site(follow='twitter') diff --git a/setup.py b/setup.py index d2d3570d..e6c8e94f 100755 --- a/setup.py +++ b/setup.py @@ -29,9 +29,10 @@ license=open('LICENSE').read(), url='https://github.com/ryanmcgrath/twython/tree/master', keywords='twitter search api tweet twython stream', - description='Actively maintained, pure Python wrapper for the Twitter API. Supports both normal and streaming Twitter APIs', + description='Actively maintained, pure Python wrapper for the \ + Twitter API. Supports both normal and streaming Twitter APIs', long_description=open('README.rst').read() + '\n\n' + - open('HISTORY.rst').read(), + open('HISTORY.rst').read(), include_package_data=True, packages=packages, classifiers=[ diff --git a/tests/config.py b/tests/config.py index d26b6e1a..65cc2f70 100644 --- a/tests/config.py +++ b/tests/config.py @@ -23,7 +23,8 @@ # Test Ids test_tweet_id = os.environ.get('TEST_TWEET_ID', '318577428610031617') test_list_slug = os.environ.get('TEST_LIST_SLUG', 'team') -test_list_owner_screen_name = os.environ.get('TEST_LIST_OWNER_SCREEN_NAME', 'twitterapi') +test_list_owner_screen_name = os.environ.get('TEST_LIST_OWNER_SCREEN_NAME', + 'twitterapi') test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick should #checkitout. If you can! #thanks Love, @__twython__ https://t.co/67pwRvY6z9', u'in_reply_to_status_id': None, u'id': 349683012054683648, u'favorite_count': 0, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [{u'id': 29251354, u'indices': [45, 57], u'id_str': u'29251354', u'screen_name': u'mikehelmick', u'name': u'Mike Helmick'}, {u'id': 1431865928, u'indices': [104, 116], u'id_str': u'1431865928', u'screen_name': u'__twython__', u'name': u'Twython'}], u'hashtags': [{u'indices': [28, 33], u'text': u'cool'}, {u'indices': [65, 76], u'text': u'checkitout'}, {u'indices': [90, 97], u'text': u'thanks'}], u'urls': [{u'url': u'http://t.co/FCmXyI6VHd', u'indices': [0, 22], u'expanded_url': u'http://google.com', u'display_url': u'google.com'}, {u'url': u'https://t.co/67pwRvY6z9', u'indices': [117, 140], u'expanded_url': u'https://github.com', u'display_url': u'github.com'}]}, u'in_reply_to_screen_name': None, u'id_str': u'349683012054683648', u'retweet_count': 0, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 1431865928, u'verified': False, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'description': {u'urls': []}}, u'followers_count': 1, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'1431865928', u'profile_background_color': u'3D3D3D', u'listed_count': 0, u'profile_background_image_url_https': u'https://si0.twimg.com/images/themes/theme1/bg.png', u'utc_offset': None, u'statuses_count': 2, u'description': u'', u'friends_count': 1, u'location': u'', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'following': False, u'geo_enabled': False, u'profile_background_image_url': u'http://a0.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'__twython__', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Twython', u'notifications': False, u'url': None, u'created_at': u'Thu May 16 01:11:09 +0000 2013', u'contributors_enabled': False, u'time_zone': None, u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'lang': u'en', u'created_at': u'Wed Jun 26 00:18:21 +0000 2013', u'in_reply_to_status_id_str': None, u'place': None} test_tweet_html = 'google.com is a #cool site, lol! @mikehelmick should #checkitout. If you can! #thanks Love, @__twython__ github.com' diff --git a/tests/test_core.py b/tests/test_core.py index 5b8a3749..98d3a476 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -28,8 +28,8 @@ def get_url(self, endpoint): return '%s/%s.json' % (self.api.api_url % self.api.api_version, endpoint) def register_response(self, method, url, body='{}', match_querystring=False, - status=200, adding_headers=None, stream=False, - content_type='application/json; charset=utf-8'): + status=200, adding_headers=None, stream=False, + content_type='application/json; charset=utf-8'): """Wrapper function for responses for simpler unit tests""" # responses uses BytesIO to hold the body so it needs to be in bytes @@ -37,7 +37,7 @@ def register_response(self, method, url, body='{}', match_querystring=False, body = bytes(body, 'UTF-8') responses.add(method, url, body, match_querystring, - status, adding_headers, stream, content_type) + status, adding_headers, stream, content_type) @responses.activate def test_request_should_handle_full_endpoint(self): @@ -77,7 +77,7 @@ def test_request_should_post_request_regardless_of_case(self): @responses.activate def test_request_should_throw_exception_with_invalid_http_method(self): """Test that request() throws an exception when an invalid HTTP method is passed""" - #TODO(cash): should Twython catch the AttributeError and throw a TwythonError + # TODO(cash): should Twython catch the AttributeError and throw a TwythonError self.assertRaises(AttributeError, self.api.request, endpoint='search/tweets', method='INVALID') @responses.activate @@ -315,5 +315,5 @@ def test_html_for_tweet_short_url(self): """Test using expanded url in HTML for Tweet displays full urls""" tweet_text = self.api.html_for_tweet(test_tweet_object, False) # Make sure HTML doesn't contain the display OR expanded url - self.assertTrue(not 'http://google.com' in tweet_text) - self.assertTrue(not 'google.com' in tweet_text) + self.assertTrue('http://google.com' not in tweet_text) + self.assertTrue('google.com' not in tweet_text) diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 6204a577..b7478d03 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -20,8 +20,10 @@ def setUp(self): 'allow_redirects': False } + # This is so we can hit coverage that Twython sets + # User-Agent for us if none is supplied oauth2_client_args = { - 'headers': {} # This is so we can hit coverage that Twython sets User-Agent for us if none is supplied + 'headers': {} } self.api = Twython(app_key, app_secret, @@ -42,7 +44,8 @@ def test_get_user_timeline(self): """Test returning timeline for authenticated user and random user succeeds""" self.api.get_user_timeline() # Authenticated User Timeline - self.api.get_user_timeline(screen_name='twitter') # Random User Timeline + self.api.get_user_timeline(screen_name='twitter') + # Random User Timeline @unittest.skip('skipping non-updated test') def test_get_protected_user_timeline_following(self): @@ -82,7 +85,8 @@ def test_show_status(self): @unittest.skip('skipping non-updated test') def test_update_and_destroy_status(self): """Test updating and deleting a status succeeds""" - status = self.api.update_status(status='Test post just to get deleted :( %s' % int(time.time())) + status = self.api.update_status(status='Test post just to get \ + deleted :( %s' % int(time.time())) self.api.destroy_status(id=status['id_str']) @unittest.skip('skipping non-updated test') @@ -117,7 +121,8 @@ def test_get_sent_messages(self): def test_send_get_and_destroy_direct_message(self): """Test sending, getting, then destory a direct message succeeds""" message = self.api.send_direct_message(screen_name=protected_twitter_1, - text='Hey d00d! %s' % int(time.time())) + text='Hey d00d! %s\ + ' % int(time.time())) self.api.get_direct_message(id=message['id_str']) self.api.destroy_direct_message(id=message['id_str']) @@ -127,7 +132,8 @@ def test_send_direct_message_to_non_follower(self): """Test sending a direct message to someone who doesn't follow you fails""" self.assertRaises(TwythonError, self.api.send_direct_message, - screen_name=protected_twitter_2, text='Yo, man! %s' % int(time.time())) + screen_name=protected_twitter_2, text='Yo, man! \ + %s' % int(time.time())) # Friends & Followers @unittest.skip('skipping non-updated test') @@ -348,7 +354,8 @@ def test_create_update_destroy_list_add_remove_list_members(self): the_list = self.api.create_list(name='Stuff %s' % int(time.time())) list_id = the_list['id_str'] - self.api.update_list(list_id=list_id, name='Stuff Renamed %s' % int(time.time())) + self.api.update_list(list_id=list_id, name='Stuff Renamed \ + %s' % int(time.time())) screen_names = ['johncena', 'xbox'] # Multi add/delete members @@ -359,7 +366,8 @@ def test_create_update_destroy_list_add_remove_list_members(self): # Single add/delete member self.api.add_list_member(list_id=list_id, screen_name='justinbieber') - self.api.delete_list_member(list_id=list_id, screen_name='justinbieber') + self.api.delete_list_member(list_id=list_id, + screen_name='justinbieber') self.api.delete_list(list_id=list_id) diff --git a/twython/api.py b/twython/api.py index 9ae69a15..3a37909a 100644 --- a/twython/api.py +++ b/twython/api.py @@ -194,12 +194,14 @@ def _request(self, url, method='GET', params=None, api_call=None): raise ExceptionType(error_message, error_code=response.status_code, - retry_after=response.headers.get('retry-after')) + retry_after=response.headers.get('retry-\ + after')) try: content = response.json() except ValueError: - raise TwythonError('Response was not valid JSON. Unable to decode.') + raise TwythonError('Response was not valid JSON. \ + Unable to decode.') return content @@ -209,7 +211,8 @@ def _get_error_message(self, response): error_message = 'An error occurred processing your request.' try: content = response.json() - # {"errors":[{"code":34,"message":"Sorry, that page does not exist"}]} + # {"errors":[{"code":34,"message":"Sorry, + # that page does not exist"}]} error_message = content['errors'][0]['message'] except ValueError: # bad json data from Twitter for an error @@ -223,13 +226,18 @@ def _get_error_message(self, response): def request(self, endpoint, method='GET', params=None, version='1.1'): """Return dict of response received from Twitter's API - :param endpoint: (required) Full url or Twitter API endpoint (e.g. search/tweets) + :param endpoint: (required) Full url or Twitter API endpoint + (e.g. search/tweets) :type endpoint: string - :param method: (optional) Method of accessing data, either GET or POST. (default GET) + :param method: (optional) Method of accessing data, either + GET or POST. (default GET) :type method: string - :param params: (optional) Dict of parameters (if any) accepted the by Twitter API endpoint you are trying to access (default None) + :param params: (optional) Dict of parameters (if any) accepted + the by Twitter API endpoint you are trying to + access (default None) :type params: dict or None - :param version: (optional) Twitter API version to access (default 1.1) + :param version: (optional) Twitter API version to access + (default 1.1) :type version: string :rtype: dict @@ -242,7 +250,8 @@ def request(self, endpoint, method='GET', params=None, version='1.1'): else: url = '%s/%s.json' % (self.api_url % version, endpoint) - content = self._request(url, method=method, params=params, api_call=url) + content = self._request(url, method=method, params=params, + api_call=url) return content @@ -258,7 +267,8 @@ def get_lastfunction_header(self, header, default_return_value=None): """Returns a specific header from the last API call This will return None if the header is not present - :param header: (required) The name of the header you want to get the value of + :param header: (required) The name of the header you want to get + the value of Most useful for the following header information: x-rate-limit-limit, @@ -268,21 +278,31 @@ def get_lastfunction_header(self, header, default_return_value=None): """ if self._last_call is None: - raise TwythonError('This function must be called after an API call. It delivers header information.') + raise TwythonError('This function must be called after an API call. \ + It delivers header information.') return self._last_call['headers'].get(header, default_return_value) - def get_authentication_tokens(self, callback_url=None, force_login=False, screen_name=''): - """Returns a dict including an authorization URL, ``auth_url``, to direct a user to - - :param callback_url: (optional) Url the user is returned to after they authorize your app (web clients only) - :param force_login: (optional) Forces the user to enter their credentials to ensure the correct users account is authorized. - :param screen_name: (optional) If forced_login is set OR user is not currently logged in, Prefills the username input box of the OAuth login screen with the given value + def get_authentication_tokens(self, callback_url=None, force_login=False, + screen_name=''): + """Returns a dict including an authorization URL, ``auth_url``, to + direct a user to + + :param callback_url: (optional) Url the user is returned to after + they authorize your app (web clients only) + :param force_login: (optional) Forces the user to enter their + credentials to ensure the correct users + account is authorized. + :param screen_name: (optional) If forced_login is set OR user is + not currently logged in, Prefills the username + input box of the OAuth login screen with the + given value :rtype: dict """ if self.oauth_version != 1: - raise TwythonError('This method can only be called when your OAuth version is 1.0.') + raise TwythonError('This method can only be called when your \ + OAuth version is 1.0.') request_args = {} if callback_url: @@ -290,15 +310,18 @@ def get_authentication_tokens(self, callback_url=None, force_login=False, screen response = self.client.get(self.request_token_url, params=request_args) if response.status_code == 401: - raise TwythonAuthError(response.content, error_code=response.status_code) + raise TwythonAuthError(response.content, + error_code=response.status_code) elif response.status_code != 200: - raise TwythonError(response.content, error_code=response.status_code) + raise TwythonError(response.content, + error_code=response.status_code) request_tokens = dict(parse_qsl(response.content.decode('utf-8'))) if not request_tokens: raise TwythonError('Unable to decode request tokens.') - oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed') == 'true' + oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed') \ + == 'true' auth_url_params = { 'oauth_token': request_tokens['oauth_token'], @@ -314,21 +337,28 @@ def get_authentication_tokens(self, callback_url=None, force_login=False, screen if callback_url and not oauth_callback_confirmed: auth_url_params['oauth_callback'] = self.callback_url - request_tokens['auth_url'] = self.authenticate_url + '?' + urlencode(auth_url_params) + request_tokens['auth_url'] = self.authenticate_url + \ + '?' + urlencode(auth_url_params) return request_tokens def get_authorized_tokens(self, oauth_verifier): - """Returns a dict of authorized tokens after they go through the :class:`get_authentication_tokens` phase. + """Returns a dict of authorized tokens after they go through the + :class:`get_authentication_tokens` phase. - :param oauth_verifier: (required) The oauth_verifier (or a.k.a PIN for non web apps) retrieved from the callback url querystring + :param oauth_verifier: (required) The oauth_verifier (or a.k.a PIN + for non web apps) retrieved from the callback url querystring :rtype: dict """ if self.oauth_version != 1: - raise TwythonError('This method can only be called when your OAuth version is 1.0.') + raise TwythonError('This method can only be called when your \ + OAuth version is 1.0.') - response = self.client.get(self.access_token_url, params={'oauth_verifier': oauth_verifier}, headers={'Content-Type': 'application/json'}) + response = self.client.get(self.access_token_url, + params={'oauth_verifier': oauth_verifier}, + headers={'Content-Type': 'application/\ + json'}) if response.status_code == 401: try: @@ -341,7 +371,8 @@ def get_authorized_tokens(self, oauth_verifier): except ValueError: content = {} - raise TwythonError(content.get('error', 'Invalid / expired Token'), error_code=response.status_code) + raise TwythonError(content.get('error', 'Invalid / expired To \ + ken'), error_code=response.status_code) authorized_tokens = dict(parse_qsl(response.content.decode('utf-8'))) if not authorized_tokens: @@ -350,12 +381,14 @@ def get_authorized_tokens(self, oauth_verifier): return authorized_tokens # pragma: no cover def obtain_access_token(self): - """Returns an OAuth 2 access token to make OAuth 2 authenticated read-only calls. + """Returns an OAuth 2 access token to make OAuth 2 authenticated + read-only calls. :rtype: string """ if self.oauth_version != 2: - raise TwythonError('This method can only be called when your OAuth version is 2.0.') + raise TwythonError('This method can only be called when your \ + OAuth version is 2.0.') data = {'grant_type': 'client_credentials'} basic_auth = HTTPBasicAuth(self.app_key, self.app_secret) @@ -377,8 +410,10 @@ def obtain_access_token(self): def construct_api_url(api_url, **params): """Construct a Twitter API url, encoded, with parameters - :param api_url: URL of the Twitter API endpoint you are attempting to construct - :param \*\*params: Parameters that are accepted by Twitter for the endpoint you're requesting + :param api_url: URL of the Twitter API endpoint you are attempting + to construct + :param \*\*params: Parameters that are accepted by Twitter for the + endpoint you're requesting :rtype: string Usage:: @@ -387,7 +422,8 @@ def construct_api_url(api_url, **params): >>> twitter = Twython() >>> api_url = 'https://api.twitter.com/1.1/search/tweets.json' - >>> constructed_url = twitter.construct_api_url(api_url, q='python', result_type='popular') + >>> constructed_url = twitter.construct_api_url(api_url, q='python', + result_type='popular') >>> print constructed_url https://api.twitter.com/1.1/search/tweets.json?q=python&result_type=popular @@ -403,7 +439,8 @@ def construct_api_url(api_url, **params): def search_gen(self, search_query, **params): # pragma: no cover warnings.warn( - 'This method is deprecated. You should use Twython.cursor instead. [eg. Twython.cursor(Twython.search, q=\'your_query\')]', + 'This method is deprecated. You should use Twython.cursor instead. \ + [eg. Twython.cursor(Twython.search, q=\'your_query\')]', TwythonDeprecationWarning, stacklevel=2 ) @@ -412,14 +449,17 @@ def search_gen(self, search_query, **params): # pragma: no cover def cursor(self, function, return_pages=False, **params): """Returns a generator for results that match a specified query. - :param function: Instance of a Twython function (Twython.get_home_timeline, Twython.search) - :param \*\*params: Extra parameters to send with your request (usually parameters accepted by the Twitter API endpoint) + :param function: Instance of a Twython function + (Twython.get_home_timeline, Twython.search) + :param \*\*params: Extra parameters to send with your request + (usually parameters accepted by the Twitter API endpoint) :rtype: generator Usage:: >>> from twython import Twython - >>> twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) + >>> twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, + OAUTH_TOKEN_SECRET) >>> results = twitter.cursor(twitter.search, q='python') >>> for result in results: @@ -427,7 +467,8 @@ def cursor(self, function, return_pages=False, **params): """ if not hasattr(function, 'iter_mode'): - raise TwythonError('Unable to create generator for Twython method "%s"' % function.__name__) + raise TwythonError('Unable to create generator for Twython \ + method "%s"' % function.__name__) while True: content = function(**params) @@ -446,22 +487,26 @@ def cursor(self, function, return_pages=False, **params): for result in results: yield result - if function.iter_mode == 'cursor' and content['next_cursor_str'] == '0': + if function.iter_mode == 'cursor' and \ + content['next_cursor_str'] == '0': raise StopIteration try: if function.iter_mode == 'id': - if not 'max_id' in params: - # Add 1 to the id because since_id and max_id are inclusive + if 'max_id' not in params: + # Add 1 to the id because since_id and + # max_id are inclusive if hasattr(function, 'iter_metadata'): - since_id = content[function.iter_metadata].get('since_id_str') + since_id = content[function.iter_metadata]\ + .get('since_id_str') else: since_id = content[0]['id_str'] params['since_id'] = (int(since_id) - 1) elif function.iter_mode == 'cursor': params['cursor'] = content['next_cursor_str'] except (TypeError, ValueError): # pragma: no cover - raise TwythonError('Unable to generate next page of search results, `page` is not a number.') + raise TwythonError('Unable to generate next page of search \ + results, `page` is not a number.') @staticmethod def unicode2utf8(text): @@ -483,11 +528,14 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False): """Return HTML for a tweet (urls, mentions, hashtags replaced with links) :param tweet: Tweet object from received from Twitter API - :param use_display_url: Use display URL to represent link (ex. google.com, github.com). Default: True - :param use_expanded_url: Use expanded URL to represent link (e.g. http://google.com). Default False + :param use_display_url: Use display URL to represent link + (ex. google.com, github.com). Default: True + :param use_expanded_url: Use expanded URL to represent link + (e.g. http://google.com). Default False If use_expanded_url is True, it overrides use_display_url. - If use_display_url and use_expanded_url is False, short url will be used (t.co/xxxxx) + If use_display_url and use_expanded_url is False, short url will + be used (t.co/xxxxx) """ if 'retweeted_status' in tweet: @@ -502,7 +550,8 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False): start, end = entity['indices'][0], entity['indices'][1] mention_html = '@%(screen_name)s' - text = text.replace(tweet['text'][start:end], mention_html % {'screen_name': entity['screen_name']}) + text = text.replace(tweet['text'][start:end], + mention_html % {'screen_name': entity['screen_name']}) # Hashtags for entity in entities['hashtags']: @@ -514,7 +563,8 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False): # Urls for entity in entities['urls']: start, end = entity['indices'][0], entity['indices'][1] - if use_display_url and entity.get('display_url') and not use_expanded_url: + if use_display_url and entity.get('display_url') \ + and not use_expanded_url: shown_url = entity['display_url'] elif use_expanded_url and entity.get('expanded_url'): shown_url = entity['expanded_url'] @@ -522,6 +572,7 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False): shown_url = entity['url'] url_html = '%s' - text = text.replace(tweet['text'][start:end], url_html % (entity['url'], shown_url)) + text = text.replace(tweet['text'][start:end], + url_html % (entity['url'], shown_url)) return text diff --git a/twython/streaming/api.py b/twython/streaming/api.py index c814acbf..47678e47 100644 --- a/twython/streaming/api.py +++ b/twython/streaming/api.py @@ -38,8 +38,11 @@ def __init__(self, app_key, app_secret, oauth_token, oauth_token_secret, retired :param retry_in: (optional) Amount of time (in secs) the previous API call should be tried again - :param client_args: (optional) Accepts some requests Session parameters and some requests Request parameters. - See http://docs.python-requests.org/en/latest/api/#sessionapi and requests section below it for details. + :param client_args: (optional) Accepts some requests Session + parameters and some requests Request parameters. + See + http://docs.python-requests.org/en/latest/api/#sessionapi + and requests section below it for details. [ex. headers, proxies, verify(SSL verification)] :param handlers: (optional) Array of message types for which corresponding handlers will be called @@ -53,11 +56,12 @@ def __init__(self, app_key, app_secret, oauth_token, oauth_token_secret, self.client_args = client_args or {} default_headers = {'User-Agent': 'Twython Streaming v' + __version__} - if not 'headers' in self.client_args: + if 'headers' not in self.client_args: # If they didn't set any headers, set our defaults for them self.client_args['headers'] = default_headers elif 'User-Agent' not in self.client_args['headers']: - # If they set headers, but didn't include User-Agent.. set it for them + # If they set headers, but didn't include User-Agent.. + # set it for them self.client_args['headers'].update(default_headers) self.client_args['timeout'] = timeout @@ -87,7 +91,8 @@ def __init__(self, app_key, app_secret, oauth_token, oauth_token_secret, self.connected = False - self.handlers = handlers if handlers else ['delete', 'limit', 'disconnect'] + self.handlers = handlers if handlers else \ + ['delete', 'limit', 'disconnect'] self.chunk_size = chunk_size @@ -103,7 +108,8 @@ def _request(self, url, method='GET', params=None): def _send(retry_counter): requests_args = {} for k, v in self.client_args.items(): - # Maybe this should be set as a class variable and only done once? + # Maybe this should be set as a class + # variable and only done once? if k in ('timeout', 'allow_redirects', 'verify'): requests_args[k] = v @@ -121,7 +127,8 @@ def _send(retry_counter): if response.status_code != 200: self.on_error(response.status_code, response.content) - if self.retry_count and (self.retry_count - retry_counter) > 0: + if self.retry_count and \ + (self.retry_count - retry_counter) > 0: time.sleep(self.retry_in) retry_counter += 1 _send(retry_counter) @@ -140,13 +147,19 @@ def _send(retry_counter): line = line.decode('utf-8') data = json.loads(line) except ValueError: # pragma: no cover - self.on_error(response.status_code, 'Unable to decode response, not valid JSON.') + self.on_error(response.status_code, + 'Unable to decode response, \ + not valid JSON.') else: if self.on_success(data): # pragma: no cover for message_type in self.handlers: if message_type in data: - handler = getattr(self, 'on_' + message_type, None) - if handler and callable(handler) and not handler(data.get(message_type)): + handler = getattr(self, + 'on_' + message_type, + None) + if handler \ + and callable(handler) \ + and not handler(data.get(message_type)): break response.close() From 54647cf0a940afedb048d0c47c67bb6562e25c9c Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Tue, 5 Aug 2014 06:15:59 -0400 Subject: [PATCH 054/165] Revert 1aa2d45..3bef603 This rolls back to commit 1aa2d451d9edc063122bfd7a88e5c104bed08017. --- examples/block_spammer.py | 20 -------------------- examples/block_user.py | 20 -------------------- examples/unblock_user.py | 20 -------------------- examples/unfollow_user.py | 20 -------------------- 4 files changed, 80 deletions(-) delete mode 100644 examples/block_spammer.py delete mode 100644 examples/block_user.py delete mode 100644 examples/unblock_user.py delete mode 100644 examples/unfollow_user.py diff --git a/examples/block_spammer.py b/examples/block_spammer.py deleted file mode 100644 index c579904c..00000000 --- a/examples/block_spammer.py +++ /dev/null @@ -1,20 +0,0 @@ -from twython import Twython, TwythonError - -# Optionally accept user data from the command line (or elsewhere). -# -# Usage: follow_user.py ryanmcgrath - -import sys - -if len(sys.argv) >= 2: - target = sys.argv[1] -else: - target = raw_input("User to follow: ") # For Python 3.x use: target = input("User to follow: ") - -# Requires Authentication as of Twitter API v1.1 -twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) - -try: - twitter.report_spam(screen_name=target, follow="true") -except TwythonError as e: - print(e) diff --git a/examples/block_user.py b/examples/block_user.py deleted file mode 100644 index a06c5f08..00000000 --- a/examples/block_user.py +++ /dev/null @@ -1,20 +0,0 @@ -from twython import Twython, TwythonError - -# Optionally accept user data from the command line (or elsewhere). -# -# Usage: follow_user.py ryanmcgrath - -import sys - -if len(sys.argv) >= 2: - target = sys.argv[1] -else: - target = raw_input("User to follow: ") # For Python 3.x use: target = input("User to follow: ") - -# Requires Authentication as of Twitter API v1.1 -twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) - -try: - twitter.create_block(screen_name=target, follow="true") -except TwythonError as e: - print(e) diff --git a/examples/unblock_user.py b/examples/unblock_user.py deleted file mode 100644 index 80ea19ca..00000000 --- a/examples/unblock_user.py +++ /dev/null @@ -1,20 +0,0 @@ -from twython import Twython, TwythonError - -# Optionally accept user data from the command line (or elsewhere). -# -# Usage: follow_user.py ryanmcgrath - -import sys - -if len(sys.argv) >= 2: - target = sys.argv[1] -else: - target = raw_input("User to follow: ") # For Python 3.x use: target = input("User to follow: ") - -# Requires Authentication as of Twitter API v1.1 -twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) - -try: - twitter.destroy_block(screen_name=target, follow="true") -except TwythonError as e: - print(e) diff --git a/examples/unfollow_user.py b/examples/unfollow_user.py deleted file mode 100644 index 2417035e..00000000 --- a/examples/unfollow_user.py +++ /dev/null @@ -1,20 +0,0 @@ -from twython import Twython, TwythonError - -# Optionally accept user data from the command line (or elsewhere). -# -# Usage: follow_user.py ryanmcgrath - -import sys - -if len(sys.argv) >= 2: - target = sys.argv[1] -else: - target = raw_input("User to follow: ") # For Python 3.x use: target = input("User to follow: ") - -# Requires Authentication as of Twitter API v1.1 -twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) - -try: - twitter.destroy_friendship(screen_name=target, follow="true") -except TwythonError as e: - print(e) From 14e9349f48cdc6f779b430c5518b59c9406389b0 Mon Sep 17 00:00:00 2001 From: Diego Allen Date: Thu, 21 Aug 2014 21:07:59 -0400 Subject: [PATCH 055/165] Add missing comma in documentation code snippet --- docs/usage/basic_usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/basic_usage.rst b/docs/usage/basic_usage.rst index 4ed2a3fa..84ffb087 100644 --- a/docs/usage/basic_usage.rst +++ b/docs/usage/basic_usage.rst @@ -22,7 +22,7 @@ Create a Twython instance with your application keys and the users OAuth tokens .. code-block:: python from twython import Twython - twitter = Twython(APP_KEY, APP_SECRET + twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) User Information From 27617181b1d41ee0af3746b8f90c4159f0dcd2bd Mon Sep 17 00:00:00 2001 From: Diego Allen Date: Sun, 31 Aug 2014 23:14:49 -0400 Subject: [PATCH 056/165] Fix issue #317 TwythonRateLimitError.retry_after is always None. --- twython/api.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/twython/api.py b/twython/api.py index 3a37909a..499b83bb 100644 --- a/twython/api.py +++ b/twython/api.py @@ -194,8 +194,7 @@ def _request(self, url, method='GET', params=None, api_call=None): raise ExceptionType(error_message, error_code=response.status_code, - retry_after=response.headers.get('retry-\ - after')) + retry_after=response.headers.get('X-Rate-Limit-Reset')) try: content = response.json() @@ -497,8 +496,7 @@ def cursor(self, function, return_pages=False, **params): # Add 1 to the id because since_id and # max_id are inclusive if hasattr(function, 'iter_metadata'): - since_id = content[function.iter_metadata]\ - .get('since_id_str') + since_id = content[function.iter_metadata].get('since_id_str') else: since_id = content[0]['id_str'] params['since_id'] = (int(since_id) - 1) From f2a535b2508b9bb1f622b34d74994838af810bfe Mon Sep 17 00:00:00 2001 From: Diego Allen Date: Mon, 1 Sep 2014 09:06:07 -0400 Subject: [PATCH 057/165] Formatting fixes --- twython/api.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/twython/api.py b/twython/api.py index 499b83bb..6e339043 100644 --- a/twython/api.py +++ b/twython/api.py @@ -192,9 +192,10 @@ def _request(self, url, method='GET', params=None, api_call=None): # app keys/user tokens ExceptionType = TwythonAuthError - raise ExceptionType(error_message, - error_code=response.status_code, - retry_after=response.headers.get('X-Rate-Limit-Reset')) + raise ExceptionType( + error_message, + error_code=response.status_code, + retry_after=response.headers.get('X-Rate-Limit-Reset')) try: content = response.json() From f22dcebbbbb319aaa484e7dcf0511f367e60a7fb Mon Sep 17 00:00:00 2001 From: Renan Cakirerk Date: Fri, 19 Sep 2014 01:48:37 -0700 Subject: [PATCH 058/165] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 143a2776..e15e73c6 100644 --- a/README.rst +++ b/README.rst @@ -137,7 +137,7 @@ Now that you have the ``oauth_verifier`` stored to a variable, you'll want to cr Once you have the final user tokens, store them in a database for later use!:: OAUTH_TOKEN = final_step['oauth_token'] - OAUTH_TOKEN_SECERT = final_step['oauth_token_secret'] + OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] For OAuth 2 (Application Only, read-only) authentication, see `our documentation `_ From bcb54c43fe371e96a6445f16f0dd4e756dd6c633 Mon Sep 17 00:00:00 2001 From: Donne Martin Date: Fri, 19 Sep 2014 17:58:27 -0400 Subject: [PATCH 059/165] Fix for Typos in README.rst #344 --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 143a2776..80bfa753 100644 --- a/README.rst +++ b/README.rst @@ -115,7 +115,7 @@ Handling the Callback If your application is a Desktop or Mobile Application *oauth_verifier* will be the PIN code -After they authorize your application to access some of their account details, they'll be redirected to the callback url you specified in ``get_autentication_tokens`` +After they authorize your application to access some of their account details, they'll be redirected to the callback url you specified in ``get_authentication_tokens`` You'll want to extract the ``oauth_verifier`` from the url. @@ -134,10 +134,10 @@ Now that you have the ``oauth_verifier`` stored to a variable, you'll want to cr final_step = twitter.get_authorized_tokens(oauth_verifier) -Once you have the final user tokens, store them in a database for later use!:: +Once you have the final user tokens, store them in a database for later use:: OAUTH_TOKEN = final_step['oauth_token'] - OAUTH_TOKEN_SECERT = final_step['oauth_token_secret'] + OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] For OAuth 2 (Application Only, read-only) authentication, see `our documentation `_ From 83379b42e6ba5249265a26e23cd2345a521fb21b Mon Sep 17 00:00:00 2001 From: Filipe Ximenes Date: Thu, 23 Oct 2014 12:03:33 -0300 Subject: [PATCH 060/165] adding upload media endpoint --- twython/endpoints.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/twython/endpoints.py b/twython/endpoints.py index bafdd7f6..9b6fca13 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -120,6 +120,16 @@ def update_status_with_media(self, **params): # pragma: no cover """ return self.post('statuses/update_with_media', params=params) + def upload_media(self, **params): + """Uploads media file to Twitter servers. The file will be available to be attached + to a status for 60 minutes. To attach to a update, pass a list of returned media ids + to the 'update_status' method using the 'media_ids' param. + + Docs: + https://dev.twitter.com/rest/public/uploading-media-multiple-photos + """ + return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) + def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded representation of a Tweet on third party sites. From 28290cd7c3b1f7f0d2b63cf912869313e9c95e29 Mon Sep 17 00:00:00 2001 From: Mertcan Mermerkaya Date: Wed, 29 Oct 2014 01:26:03 +0200 Subject: [PATCH 061/165] Update advanced_usage.rst Fixed wrong variable name in function arguments. --- docs/usage/advanced_usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index db363019..b95cdb3a 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -55,7 +55,7 @@ Posting a Status with an Editing Image # unable to be read image_io.seek(0) - twitter.update_status_with_media(media=photo, status='Check out my edited image!') + twitter.update_status_with_media(media=image_io, status='Check out my edited image!') Search Generator From 8548a312389703d02722bdde6fbfff587133a771 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Tue, 12 Aug 2014 13:38:20 -0400 Subject: [PATCH 062/165] Changing versions, adding AUTHORS & HISTORY --- AUTHORS.rst | 11 +++++++++-- HISTORY.rst | 12 ++++++++++++ docs/conf.py | 4 ++-- setup.py | 4 ++-- twython/__init__.py | 2 +- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 6cddac60..ab867846 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -27,7 +27,7 @@ Patches and Suggestions - `Jonathan Elsas `_, Fix for original Streaming API stub causing import errors. - `LuqueDaniel `_, Extended example code where necessary. - `Mesar Hameed `_, Commit to swap ``__getattr__`` trick for a more debuggable solution. -- `Remy DeCausemaker `_, PEP-8 contributions. +- `Remy DeCausemaker `_, PEP 8 contributions. - `mckellister `_ Twitter Spring 2012 Clean Up fixes to ``Exception`` raised by Twython (Rate Limits, etc). - `Tatz Tsuchiya `_, Fix for ``lambda`` scoping in key injection phase. - `Mohammed ALDOUB `_, Fixes for ``http/https`` access endpoints. @@ -35,7 +35,7 @@ Patches and Suggestions - `Terry Jones `_, Error cleanup and Exception processing in 2.3.0. - `Leandro Ferreira `_, Fix for double-encoding of search queries in 2.3.0. - `Chris Brown `_, Updated to use v1.1 endpoints over v1 -- `Virendra Rajput `_, Fixed unicode (json) encoding in twython.py 2.7.2. +- `Virendra Rajput `_, Fixed unicode (json) encoding in twython.py 2.7.2. - `Paul Solbach `_, fixed requirement for oauth_verifier - `Greg Nofi `_, fixed using built-in Exception attributes for storing & retrieving error message - `Jonathan Vanasco `_, Debugging support, error_code tracking, Twitter error API tracking, other fixes @@ -43,3 +43,10 @@ Patches and Suggestions - `Ruben Varela Rosa `_, Fixed search example - `Oleg Anashkin `_, streaming ``handlers`` functionality - `Luis Alberto Santana `_, Fixed issue where Twython was unnecessarily disabling compression +- `Cory Dolphin `_, Added retry_after attribute to TwythonRateLimitError +- `Natan L `_, Fixed typo in documentation +- `Cash Costello `_, Moved tests to use `responsoes`, fixed typos in documentation +- `Joe Cabrera `_, PEP 8 contributions +- `bsbkeven `_, Added `lookup_status` function to `endpoints.py` +- `drevicko `_, Added option to yield full page vs individual results in `cursor` diff --git a/HISTORY.rst b/HISTORY.rst index 1259227a..4be8b5a2 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,18 @@ History ------- +3.2.0 (2014-08-xx) +++++++++++++++++++ +- PEP8'd some code +- Added `lookup_status` function to `endpoints.py` +- Added keyword argument to `cursor` to return full pages rather than individual results +- `cursor` now uses while loop rather than recursion +- Fixed issue where Twython was unnecessarily disabling compression +- Using `responses` to mock API calls in tests +- Fixed some typos in documentation +- Added retry_after attribute to TwythonRateLimitError + + 3.1.2 (2013-12-05) ++++++++++++++++++ diff --git a/docs/conf.py b/docs/conf.py index b0b98eb4..a0f9ef4f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '3.1.2' +version = '3.2.0' # The full version, including alpha/beta/rc tags. -release = '3.1.2' +release = '3.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index e6c8e94f..09ea8ec6 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.1.2' +__version__ = '3.2.0' packages = [ 'twython', @@ -23,7 +23,7 @@ setup( name='twython', version=__version__, - install_requires=['requests==2.1.0', 'requests_oauthlib==0.4.0'], + install_requires=['requests2.1.0', 'requests_oauthlib==0.4.0'], author='Ryan McGrath', author_email='ryan@venodesigns.net', license=open('LICENSE').read(), diff --git a/twython/__init__.py b/twython/__init__.py index e0920a65..a79667d0 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ """ __author__ = 'Ryan McGrath ' -__version__ = '3.1.2' +__version__ = '3.2.0' from .api import Twython from .streaming import TwythonStreamer From 23a0b62f2c2464d446444d98dadf466288facbcb Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Thu, 30 Oct 2014 11:07:18 -0400 Subject: [PATCH 063/165] Update AUTHORS, HISTORY, DeprecationWarning on update_status_with_media --- AUTHORS.rst | 4 ++++ HISTORY.rst | 6 ++++-- twython/endpoints.py | 9 +++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index ab867846..24bd911d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -50,3 +50,7 @@ Patches and Suggestions - `Joe Cabrera `_, PEP 8 contributions - `bsbkeven `_, Added `lookup_status` function to `endpoints.py` - `drevicko `_, Added option to yield full page vs individual results in `cursor` +- `Filipe A Ximenes `_, Added `upload_media` function to `endpoints.py` +- `Mertcan Mermerkaya `_, Fixed code example in documentation +- `Donne Martin `_, Fixed typos in `README.rst` +- `Diego Allen `_, Add missing comma in documentation code snippet diff --git a/HISTORY.rst b/HISTORY.rst index 4be8b5a2..03567f4c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -11,8 +11,10 @@ History - `cursor` now uses while loop rather than recursion - Fixed issue where Twython was unnecessarily disabling compression - Using `responses` to mock API calls in tests -- Fixed some typos in documentation -- Added retry_after attribute to TwythonRateLimitError +- Fixed some typos in documentation +- Added `retry_after` attribute to `TwythonRateLimitError` +- Added `upload_media` method to `Twython` in favor of `update_with_media` +- Deprecating `update_with_media` per Twitter API 1.1 (https://dev.twitter.com/rest/reference/post/statuses/update_with_media) 3.1.2 (2013-12-05) diff --git a/twython/endpoints.py b/twython/endpoints.py index 9b6fca13..444a920a 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -14,6 +14,10 @@ https://dev.twitter.com/docs/api/1.1 """ +import warnings + +from .advisory import TwythonDeprecationWarning + class EndpointsMixin(object): # Timelines @@ -118,6 +122,11 @@ def update_status_with_media(self, **params): # pragma: no cover https://dev.twitter.com/docs/api/1.1/post/statuses/update_with_media """ + warnings.warn( + 'This method is deprecated. You should use Twython.upload_media instead.', + TwythonDeprecationWarning, + stacklevel=2 + ) return self.post('statuses/update_with_media', params=params) def upload_media(self, **params): From e42728166095b1ad1b9fa07dfb00ce0bcf245a67 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Thu, 30 Oct 2014 11:07:23 -0400 Subject: [PATCH 064/165] Fixes #305 --- twython/api.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/twython/api.py b/twython/api.py index 6e339043..0b421c35 100644 --- a/twython/api.py +++ b/twython/api.py @@ -9,6 +9,8 @@ dealing with the Twitter API """ +import warnings + import requests from requests.auth import HTTPBasicAuth from requests_oauthlib import OAuth1, OAuth2 @@ -20,8 +22,6 @@ from .exceptions import TwythonError, TwythonAuthError, TwythonRateLimitError from .helpers import _transparent_params -import warnings - warnings.simplefilter('always', TwythonDeprecationWarning) # For Python 2.7 > @@ -243,9 +243,12 @@ def request(self, endpoint, method='GET', params=None, version='1.1'): :rtype: dict """ + if endpoint.startswith('http://'): + raise TwythonError('api.twitter.com is restricted to SSL/TLS traffic.') + # In case they want to pass a full Twitter URL # i.e. https://api.twitter.com/1.1/search/tweets.json - if endpoint.startswith('http://') or endpoint.startswith('https://'): + if endpoint.startswith('https://'): url = endpoint else: url = '%s/%s.json' % (self.api_url % version, endpoint) From 02d1a946c19b52229b7522d1c4acf227b5aaf284 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Thu, 30 Oct 2014 16:49:03 -0400 Subject: [PATCH 065/165] Unpin requests --- requirements.txt | 6 +++--- setup.py | 2 +- tests/test_core.py | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/requirements.txt b/requirements.txt index 917c31ac..126cbe45 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ coverage==3.6.0 -requests==2.1.0 -requests_oauthlib==0.4.0 +requests>=2.1.0 +requests_oauthlib>=0.4.0 python-coveralls==2.1.0 nose-cov==1.6 -responses==0.2.0 +responses==0.3.0 diff --git a/setup.py b/setup.py index 09ea8ec6..e203cc98 100755 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup( name='twython', version=__version__, - install_requires=['requests2.1.0', 'requests_oauthlib==0.4.0'], + install_requires=['requests>=2.1.0', 'requests_oauthlib>=0.4.0'], author='Ryan McGrath', author_email='ryan@venodesigns.net', license=open('LICENSE').read(), diff --git a/tests/test_core.py b/tests/test_core.py index 98d3a476..1aa0eea3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -262,16 +262,16 @@ def test_get_lastfunction_header_should_return_header(self): """Test getting last specific header of the last API call works""" endpoint = 'statuses/home_timeline' url = self.get_url(endpoint) - self.register_response(responses.GET, url, adding_headers={'x-rate-limit-remaining': 37}) + self.register_response(responses.GET, url, adding_headers={'x-rate-limit-remaining': '37'}) self.api.get(endpoint) value = self.api.get_lastfunction_header('x-rate-limit-remaining') - self.assertEqual(37, value) + self.assertEqual('37', value) value2 = self.api.get_lastfunction_header('does-not-exist') self.assertIsNone(value2) - value3 = self.api.get_lastfunction_header('not-there-either', 96) - self.assertEqual(96, value3) + value3 = self.api.get_lastfunction_header('not-there-either', '96') + self.assertEqual('96', value3) def test_get_lastfunction_header_should_raise_error_when_no_previous_call(self): """Test attempting to get a header when no API call was made raises a TwythonError""" @@ -286,7 +286,7 @@ def test_sends_correct_accept_encoding_header(self): self.api.get(endpoint) - self.assertEqual(b'gzip, deflate, compress', responses.calls[0].request.headers['Accept-Encoding']) + self.assertEqual(b'gzip, deflate', responses.calls[0].request.headers['Accept-Encoding']) # Static methods def test_construct_api_url(self): From 1d383d93033976056c7afb5ea2ba101ba78770ea Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Thu, 30 Oct 2014 16:49:58 -0400 Subject: [PATCH 066/165] Update HISTORY.rst --- HISTORY.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/HISTORY.rst b/HISTORY.rst index 03567f4c..4454d4c9 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -15,6 +15,7 @@ History - Added `retry_after` attribute to `TwythonRateLimitError` - Added `upload_media` method to `Twython` in favor of `update_with_media` - Deprecating `update_with_media` per Twitter API 1.1 (https://dev.twitter.com/rest/reference/post/statuses/update_with_media) +- Unpin `requests` and `requests-oauthlib` in `requirements.txt` 3.1.2 (2013-12-05) From 134414c87a3aa7b26b1800c188de412aca162b87 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Thu, 30 Oct 2014 17:05:14 -0400 Subject: [PATCH 067/165] Update HISTORY.rst --- HISTORY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 4454d4c9..f9d129cc 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,7 +3,7 @@ History ------- -3.2.0 (2014-08-xx) +3.2.0 (2014-10-30) ++++++++++++++++++ - PEP8'd some code - Added `lookup_status` function to `endpoints.py` From 123b8f4f74a7e1322bc602f66e6e962dc5ff767a Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Sun, 2 Nov 2014 10:41:56 +1100 Subject: [PATCH 068/165] Added the create, destroy, list and list IDs mute(s) endpoints. Roughly modelled on the similar-ish block user functions (note: the "mutes/users" instead of just "mutes" appears to be intentional by Twitter). Also added myself to AUTHORS.rst because I reckon adding API bits makes the grade. ;) --- AUTHORS.rst | 1 + twython/endpoints.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 24bd911d..8a16050c 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -54,3 +54,4 @@ Patches and Suggestions - `Mertcan Mermerkaya `_, Fixed code example in documentation - `Donne Martin `_, Fixed typos in `README.rst` - `Diego Allen `_, Add missing comma in documentation code snippet +- `Ben McGinnes `_, Added mute API endpoints, a couple of examples, random bits. diff --git a/twython/endpoints.py b/twython/endpoints.py index 444a920a..7ac99612 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -523,6 +523,46 @@ def get_profile_banner_sizes(self, **params): """ return self.get('users/profile_banner', params=params) + def list_mutes(self, **params): + """Returns a collection of user objects that the authenticating user + is muting. + + Docs: https://dev.twitter.com/docs/api/1.1/get/mutes/users/list + + """ + return self.get('mutes/users/list', params=params) + list_mutes.iter_mode = 'cursor' + list_mutes.iter_key = 'users' + + def list_mute_ids(self, **params): + """Returns an array of numeric user ids the authenticating user + is muting. + + Docs: https://dev.twitter.com/docs/api/1.1/get/mutes/users/ids + + """ + return self.get('mutes/users/ids', params=params) + list_mutes_ids.iter_mode = 'cursor' + list_mutes_ids.iter_key = 'ids' + + def create_mute(self, **params): + """Mutes the specified user, preventing their tweets appearing + in the authenticating user's timeline. + + Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/create + + """ + return self.post('mutes/users/create', params=params) + + def destroy_mute(self, **params): + """Un-mutes the user specified in the user or ID parameter for + the authenticating user. + + Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/destroy + + """ + return self.post('mutes/users/destroy', params=params) + # Suggested Users def get_user_suggestions_by_slug(self, **params): """Access the users in a given category of the Twitter suggested user list. From b5847e3e84f76a9659305580e9042d33af0fa870 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Sun, 2 Nov 2014 14:15:37 +1100 Subject: [PATCH 069/165] Added muting to test suite. I should have checked for this earlier, ah well, 'tis here now. Again, basically a copy of the blocking code updated for muting. Excellent target to test it on too. --- tests/test_endpoints.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index b7478d03..aa79998e 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -305,6 +305,28 @@ def test_get_profile_banner_sizes(self): we have not uploaded a profile banner""" self.assertRaises(TwythonError, self.api.get_profile_banner_sizes) + @unittest.skip('skipping non-updated test') + def test_list_mutes(self): + """Test listing users who are muted by the authenticated user + succeeds""" + self.api.list_mutes() + + @unittest.skip('skipping non-updated test') + def test_list_mute_ids(self): + """Test listing user ids who are muted by the authenticated user + succeeds""" + self.api.list_mute_ids() + + @unittest.skip('skipping non-updated test') + def test_create_mute(self): + """Test muting a user succeeds""" + self.api.create_mute(screen_name='justinbieber') + + @unittest.skip('skipping non-updated test') + def test_destroy_mute(self): + """Test muting a user succeeds""" + self.api.destroy_mute(screen_name='justinbieber') + # Suggested Users @unittest.skip('skipping non-updated test') def test_get_user_suggestions_by_slug(self): From 036760bfd4a3d06b534be06bc3a56843ccb84868 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Sun, 2 Nov 2014 16:04:39 +1100 Subject: [PATCH 070/165] Fixed typo with list_mute_ids. --- twython/endpoints.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 7ac99612..81aa582f 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -542,8 +542,8 @@ def list_mute_ids(self, **params): """ return self.get('mutes/users/ids', params=params) - list_mutes_ids.iter_mode = 'cursor' - list_mutes_ids.iter_key = 'ids' + list_mute_ids.iter_mode = 'cursor' + list_mute_ids.iter_key = 'ids' def create_mute(self, **params): """Mutes the specified user, preventing their tweets appearing From 245873b92f1b14328d93615f13864023d2df423c Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Mon, 3 Nov 2014 17:24:29 -0500 Subject: [PATCH 071/165] Update HISTORY.rst --- HISTORY.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index f9d129cc..a7c961d3 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,10 @@ History ------- +3.3.0 (2014-xx-xx) +++++++++++++++++++ +- Added support for muting users + 3.2.0 (2014-10-30) ++++++++++++++++++ - PEP8'd some code From 8eb8c2dd556bb0efed9cd8f0e77ccfa59e7c0d82 Mon Sep 17 00:00:00 2001 From: Derek Hu Date: Wed, 19 Nov 2014 01:09:02 -0500 Subject: [PATCH 072/165] Remove redundant checking for oauth_token & oauth_token_secret --- twython/api.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/twython/api.py b/twython/api.py index 0b421c35..e7e0046a 100644 --- a/twython/api.py +++ b/twython/api.py @@ -102,15 +102,10 @@ def __init__(self, app_key=None, app_secret=None, oauth_token=None, auth = None if oauth_version == 1: # User Authentication is through OAuth 1 - if self.app_key is not None and self.app_secret is not None and \ - self.oauth_token is None and self.oauth_token_secret is None: - auth = OAuth1(self.app_key, self.app_secret) - - if self.app_key is not None and self.app_secret is not None and \ - self.oauth_token is not None and self.oauth_token_secret is \ - not None: + if self.app_key is not None and self.app_secret is not None: auth = OAuth1(self.app_key, self.app_secret, - self.oauth_token, self.oauth_token_secret) + self.oauth_token, self.oauth_token_secret) + elif oauth_version == 2 and self.access_token: # Application Authentication is through OAuth 2 token = {'token_type': token_type, From 3cd88a09e691b630e1bb99e739ec72a1e3e1838f Mon Sep 17 00:00:00 2001 From: ping Date: Tue, 2 Dec 2014 17:54:52 +0800 Subject: [PATCH 073/165] Format media entities in Twython.html_for_tweet() --- docs/usage/special_functions.rst | 5 +++-- tests/config.py | 4 ++-- twython/api.py | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/usage/special_functions.rst b/docs/usage/special_functions.rst index f0762c01..bd51f78a 100644 --- a/docs/usage/special_functions.rst +++ b/docs/usage/special_functions.rst @@ -72,15 +72,16 @@ The above code takes all the tweets from a specific users timeline, loops over t So: - http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick should #checkitout. If you can! #thanks Love, @__twython__ https://t.co/67pwRvY6z9 + http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71 will be replaced with: - google.com is a #cool site, lol! @mikehelmick should #checkitout. If you can! #thanks Love, @__twython__ github.com + google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71 .. note:: When converting the string to HTML we add a class to each HTML tag so that you can maninpulate the DOM later on. - For urls that are replaced we add ``class="twython-url"`` to the anchor tag +- For media urls that are replaced we add ``class="twython-media"`` to the anchor tag - For user mentions that are replaced we add ``class="twython-mention"`` to the anchor tag - For hashtags that are replaced we add ``class="twython-hashtag"`` to the anchor tag diff --git a/tests/config.py b/tests/config.py index 65cc2f70..21baa69b 100644 --- a/tests/config.py +++ b/tests/config.py @@ -26,5 +26,5 @@ test_list_owner_screen_name = os.environ.get('TEST_LIST_OWNER_SCREEN_NAME', 'twitterapi') -test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick should #checkitout. If you can! #thanks Love, @__twython__ https://t.co/67pwRvY6z9', u'in_reply_to_status_id': None, u'id': 349683012054683648, u'favorite_count': 0, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [{u'id': 29251354, u'indices': [45, 57], u'id_str': u'29251354', u'screen_name': u'mikehelmick', u'name': u'Mike Helmick'}, {u'id': 1431865928, u'indices': [104, 116], u'id_str': u'1431865928', u'screen_name': u'__twython__', u'name': u'Twython'}], u'hashtags': [{u'indices': [28, 33], u'text': u'cool'}, {u'indices': [65, 76], u'text': u'checkitout'}, {u'indices': [90, 97], u'text': u'thanks'}], u'urls': [{u'url': u'http://t.co/FCmXyI6VHd', u'indices': [0, 22], u'expanded_url': u'http://google.com', u'display_url': u'google.com'}, {u'url': u'https://t.co/67pwRvY6z9', u'indices': [117, 140], u'expanded_url': u'https://github.com', u'display_url': u'github.com'}]}, u'in_reply_to_screen_name': None, u'id_str': u'349683012054683648', u'retweet_count': 0, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 1431865928, u'verified': False, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'description': {u'urls': []}}, u'followers_count': 1, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'1431865928', u'profile_background_color': u'3D3D3D', u'listed_count': 0, u'profile_background_image_url_https': u'https://si0.twimg.com/images/themes/theme1/bg.png', u'utc_offset': None, u'statuses_count': 2, u'description': u'', u'friends_count': 1, u'location': u'', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'following': False, u'geo_enabled': False, u'profile_background_image_url': u'http://a0.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'__twython__', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Twython', u'notifications': False, u'url': None, u'created_at': u'Thu May 16 01:11:09 +0000 2013', u'contributors_enabled': False, u'time_zone': None, u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'lang': u'en', u'created_at': u'Wed Jun 26 00:18:21 +0000 2013', u'in_reply_to_status_id_str': None, u'place': None} -test_tweet_html = 'google.com is a #cool site, lol! @mikehelmick should #checkitout. If you can! #thanks Love, @__twython__ github.com' +test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71', u'in_reply_to_status_id': None, u'id': 349683012054683648, u'favorite_count': 0, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [{u'id': 29251354, u'indices': [45, 57], u'id_str': u'29251354', u'screen_name': u'mikehelmick', u'name': u'Mike Helmick'}, {u'id': 1431865928, u'indices': [81, 93], u'id_str': u'1431865928', u'screen_name': u'__twython__', u'name': u'Twython'}], u'hashtags': [{u'indices': [28, 33], u'text': u'cool'}, {u'indices': [62, 73], u'text': u'checkitout'}], u'urls': [{u'url': u'http://t.co/FCmXyI6VHd', u'indices': [0, 22], u'expanded_url': u'http://google.com', u'display_url': u'google.com'}, {u'url': u'https://t.co/67pwRvY6z9', u'indices': [94, 117], u'expanded_url': u'https://github.com', u'display_url': u'github.com'}], u'media': [{u'id': 537884378513162240, u'id_str': u'537884378513162240', u'indices': [118, 140], u'media_url': u'http://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'media_url_https': u'https://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'url': u'http://t.co/N6InAO4B71', u'display_url': u'pic.twitter.com/N6InAO4B71', u'expanded_url': u'http://twitter.com/pingofglitch/status/537884380060844032/photo/1', u'type': u'photo', u'sizes': {u'large': {u'w': 1024, u'h': 640, u'resize': u'fit'}, u'thumb': {u'w': 150, u'h': 150, u'resize': u'crop'}, u'medium': {u'w': 600, u'h': 375, u'resize': u'fit'}, u'small': {u'w': 340, u'h': 212, u'resize': u'fit'}}}]}, u'in_reply_to_screen_name': None, u'id_str': u'349683012054683648', u'retweet_count': 0, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 1431865928, u'verified': False, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'description': {u'urls': []}}, u'followers_count': 1, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'1431865928', u'profile_background_color': u'3D3D3D', u'listed_count': 0, u'profile_background_image_url_https': u'https://si0.twimg.com/images/themes/theme1/bg.png', u'utc_offset': None, u'statuses_count': 2, u'description': u'', u'friends_count': 1, u'location': u'', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'following': False, u'geo_enabled': False, u'profile_background_image_url': u'http://a0.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'__twython__', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Twython', u'notifications': False, u'url': None, u'created_at': u'Thu May 16 01:11:09 +0000 2013', u'contributors_enabled': False, u'time_zone': None, u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'lang': u'en', u'created_at': u'Wed Jun 26 00:18:21 +0000 2013', u'in_reply_to_status_id_str': None, u'place': None} +test_tweet_html = 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71' diff --git a/twython/api.py b/twython/api.py index 0b421c35..51505684 100644 --- a/twython/api.py +++ b/twython/api.py @@ -577,4 +577,20 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False): text = text.replace(tweet['text'][start:end], url_html % (entity['url'], shown_url)) + # Media + if 'media' in entities: + for entity in entities['media']: + start, end = entity['indices'][0], entity['indices'][1] + if use_display_url and entity.get('display_url') \ + and not use_expanded_url: + shown_url = entity['display_url'] + elif use_expanded_url and entity.get('expanded_url'): + shown_url = entity['expanded_url'] + else: + shown_url = entity['url'] + + url_html = '%s' + text = text.replace(tweet['text'][start:end], + url_html % (entity['url'], shown_url)) + return text From 14526cb3df40f334428a27d91985a4a7357a8108 Mon Sep 17 00:00:00 2001 From: Mirat Can Bayrak Date: Tue, 16 Dec 2014 13:40:36 +0000 Subject: [PATCH 074/165] Fixed type errror on api.py --- twython/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/twython/api.py b/twython/api.py index 51505684..d2d5c03f 100644 --- a/twython/api.py +++ b/twython/api.py @@ -214,6 +214,8 @@ def _get_error_message(self, response): # {"errors":[{"code":34,"message":"Sorry, # that page does not exist"}]} error_message = content['errors'][0]['message'] + except TypeError: + error_message = content['errors'] except ValueError: # bad json data from Twitter for an error pass From db738cf41e0d2d1a087393930b48f086d4ebc2fe Mon Sep 17 00:00:00 2001 From: Jeremy Keen Date: Fri, 13 Feb 2015 12:05:28 -0800 Subject: [PATCH 075/165] search_gen in Advanced usage deprecated Updated advanced usage documentation to use cursor instead of search_gen --- docs/usage/advanced_usage.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index b95cdb3a..ff61166e 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -67,9 +67,13 @@ That being said, Twython offers a generator for search results and can be access .. code-block:: python - search = twitter.search_gen('python') - for result in search: - print result + from twython import Twython + twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, + OAUTH_TOKEN_SECRET) + + results = twitter.cursor(twitter.search, q='python') + for result in results: + print result Manipulate the Request (headers, proxies, etc.) ----------------------------------------------- From d70fa54375fd9172d4ce3b3110a2639c47476ad3 Mon Sep 17 00:00:00 2001 From: shuuji3 Date: Sat, 14 Feb 2015 17:44:45 +0900 Subject: [PATCH 076/165] Fix a broken link to the document of media_upload. --- twython/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 81aa582f..d0add16a 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -135,7 +135,7 @@ def upload_media(self, **params): to the 'update_status' method using the 'media_ids' param. Docs: - https://dev.twitter.com/rest/public/uploading-media-multiple-photos + https://dev.twitter.com/rest/public/uploading-media """ return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) From c0dae9a63ae4babad83b7e1c5c33a4adf2716a95 Mon Sep 17 00:00:00 2001 From: floydsoft Date: Mon, 23 Feb 2015 23:13:21 +0800 Subject: [PATCH 077/165] Update special_functions.rst --- docs/usage/special_functions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/special_functions.rst b/docs/usage/special_functions.rst index bd51f78a..b52a2453 100644 --- a/docs/usage/special_functions.rst +++ b/docs/usage/special_functions.rst @@ -37,7 +37,7 @@ The New Way twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) - results = twitter.cursor(t.search, q='twitter') + results = twitter.cursor(twitter.search, q='twitter') for result in results: print result['id_str'] From e0a2dfbbb0d53e816ea39220182a5ba6be5d7a54 Mon Sep 17 00:00:00 2001 From: Ben Bertka Date: Tue, 24 Mar 2015 11:00:49 -0700 Subject: [PATCH 078/165] Added dynamic filtering --- twython/streaming/types.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/twython/streaming/types.py b/twython/streaming/types.py index 39a9ccbf..c69baa93 100644 --- a/twython/streaming/types.py +++ b/twython/streaming/types.py @@ -51,6 +51,7 @@ class TwythonStreamerTypesStatuses(object): """ def __init__(self, streamer): self.streamer = streamer + self.params = None def filter(self, **params): """Stream statuses/filter @@ -87,3 +88,20 @@ def firehose(self, **params): url = 'https://stream.twitter.com/%s/statuses/firehose.json' \ % self.streamer.api_version self.streamer._request(url, params=params) + + def set_dynamic_filter(self, **params): + """Set/update statuses/filter + + :param \*\*params: Parameters to send with your stream request + + Accepted params found at: + https://dev.twitter.com/docs/api/1.1/post/statuses/filter + """ + self.params = params + + def dynamic_filter(self): + """Stream statuses/filter with dynamic parameters""" + + url = 'https://stream.twitter.com/%s/statuses/filter.json' \ + % self.streamer.api_version + self.streamer._request(url, 'POST', params=self.params) From 6d1c82b594256992cabff4bbc671c54b92d8ca4e Mon Sep 17 00:00:00 2001 From: Ben Bertka Date: Tue, 24 Mar 2015 11:36:08 -0700 Subject: [PATCH 079/165] Fixed indents for dynamic filtering in types.py --- twython/streaming/types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twython/streaming/types.py b/twython/streaming/types.py index c69baa93..aa6b9adb 100644 --- a/twython/streaming/types.py +++ b/twython/streaming/types.py @@ -51,7 +51,7 @@ class TwythonStreamerTypesStatuses(object): """ def __init__(self, streamer): self.streamer = streamer - self.params = None + self.params = None def filter(self, **params): """Stream statuses/filter @@ -97,7 +97,7 @@ def set_dynamic_filter(self, **params): Accepted params found at: https://dev.twitter.com/docs/api/1.1/post/statuses/filter """ - self.params = params + self.params = params def dynamic_filter(self): """Stream statuses/filter with dynamic parameters""" From 4c213ea10d948f30b462b9421dc2c6864acf12b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Polykanine=20A=2EK=2EA=2E=20Menelion=20Elens=C3=BA?= =?UTF-8?q?l=C3=AB?= Date: Thu, 9 Apr 2015 00:55:24 +0300 Subject: [PATCH 080/165] Corrected the docs URL for uploading media, fixes #375 --- twython/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index d0add16a..60663cac 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -135,7 +135,7 @@ def upload_media(self, **params): to the 'update_status' method using the 'media_ids' param. Docs: - https://dev.twitter.com/rest/public/uploading-media + https://dev.twitter.com/rest/reference/post/media/upload """ return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) From 4e6963d8cc8724a9a58804c6c866cef46fb385bf Mon Sep 17 00:00:00 2001 From: Will Thompson Date: Wed, 17 Jun 2015 16:27:31 +0100 Subject: [PATCH 081/165] =?UTF-8?q?special=5Ffunctions.rst:=20typo:=20exce?= =?UTF-8?q?pts=20=E2=86=92=20accepts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/usage/special_functions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/special_functions.rst b/docs/usage/special_functions.rst index b52a2453..c89da384 100644 --- a/docs/usage/special_functions.rst +++ b/docs/usage/special_functions.rst @@ -85,7 +85,7 @@ will be replaced with: - For user mentions that are replaced we add ``class="twython-mention"`` to the anchor tag - For hashtags that are replaced we add ``class="twython-hashtag"`` to the anchor tag -This function excepts two parameters: ``use_display_url`` and ``use_expanded_url`` +This function accepts two parameters: ``use_display_url`` and ``use_expanded_url`` By default, ``use_display_url`` is ``True``. Meaning the link displayed in the tweet text will appear as (ex. google.com, github.com) If ``use_expanded_url`` is ``True``, it overrides ``use_display_url``. The urls will then be displayed as (ex. http://google.com, https://github.com) If ``use_display_url`` and ``use_expanded_url`` are ``False``, short url will be used (t.co/xxxxx) From e68ba98c8fe9b4feceaaca69c3a557a7d218e8b4 Mon Sep 17 00:00:00 2001 From: kf <7kfpun@gmail.com> Date: Tue, 7 Jul 2015 22:16:42 +0800 Subject: [PATCH 082/165] missing commas --- docs/usage/advanced_usage.rst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index ff61166e..6042b255 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -16,7 +16,7 @@ Create a Twython instance with your application keys and the users OAuth tokens .. code-block:: python from twython import Twython - twitter = Twython(APP_KEY, APP_SECRET + twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) Updating Status with Image @@ -68,12 +68,12 @@ That being said, Twython offers a generator for search results and can be access .. code-block:: python from twython import Twython - twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, - OAUTH_TOKEN_SECRET) + twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, + OAUTH_TOKEN_SECRET) - results = twitter.cursor(twitter.search, q='python') - for result in results: - print result + results = twitter.cursor(twitter.search, q='python') + for result in results: + print result Manipulate the Request (headers, proxies, etc.) ----------------------------------------------- @@ -94,8 +94,8 @@ Here is an example of sending custom headers to a Twitter API request: } } - twitter = Twython(APP_KEY, APP_SECRET - OAUTH_TOKEN, OAUTH_TOKEN_SECRET + twitter = Twython(APP_KEY, APP_SECRET, + OAUTH_TOKEN, OAUTH_TOKEN_SECRET, client_args=client_args) Here is an example of sending the request through proxies: @@ -111,8 +111,8 @@ Here is an example of sending the request through proxies: } } - twitter = Twython(APP_KEY, APP_SECRET - OAUTH_TOKEN, OAUTH_TOKEN_SECRET + twitter = Twython(APP_KEY, APP_SECRET, + OAUTH_TOKEN, OAUTH_TOKEN_SECRET, client_args=client_args) or both (and set a timeout variable): @@ -132,8 +132,8 @@ or both (and set a timeout variable): 'timeout': 300, } - twitter = Twython(APP_KEY, APP_SECRET - OAUTH_TOKEN, OAUTH_TOKEN_SECRET + twitter = Twython(APP_KEY, APP_SECRET, + OAUTH_TOKEN, OAUTH_TOKEN_SECRET, client_args=client_args) Access Headers of Previous Call @@ -146,7 +146,7 @@ If you wish to access headers (ex. x-rate-limit-remaining, x-rate-limit-reset, c from twython import Twython - twitter = Twython(APP_KEY, APP_SECRET + twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) twitter.get_home_timeline() From 1f3eab6a3713161804f242f0159c515da772ddbe Mon Sep 17 00:00:00 2001 From: David Beitey Date: Wed, 8 Jul 2015 20:26:05 +1000 Subject: [PATCH 083/165] Update uploading image advanced examples Previously, these examples were using deprecated API endpoints. This updates the examples accordingly. --- docs/usage/advanced_usage.rst | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index ff61166e..e447e8db 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -22,17 +22,24 @@ Create a Twython instance with your application keys and the users OAuth tokens Updating Status with Image -------------------------- -Documentation: https://dev.twitter.com/docs/api/1.1/post/statuses/update_with_media +This uploads an image as a media object and associates it with a status update. .. code-block:: python photo = open('/path/to/file/image.jpg', 'rb') - twitter.update_status_with_media(status='Checkout this cool image!', media=photo) + response = twitter.upload_media(media=photo) + twitter.update_status(status='Checkout this cool image!', media_ids=[response['media_id']]) + +Documentation: + +* https://dev.twitter.com/rest/reference/post/statuses/update +* https://dev.twitter.com/rest/reference/post/media/upload Posting a Status with an Editing Image -------------------------------------- - This example resizes an image +This example resizes an image, then uploads it as a media object and associates it +with a status update. .. code-block:: python @@ -55,7 +62,9 @@ Posting a Status with an Editing Image # unable to be read image_io.seek(0) - twitter.update_status_with_media(media=image_io, status='Check out my edited image!') + + response = twitter.upload_media(media=image_io) + twitter.update_status(status='Checkout this cool image!', media_ids=[response['media_id']]) Search Generator From dff85ace497f6fcd633fe07bce2f465a086e43d1 Mon Sep 17 00:00:00 2001 From: Davis Silverman Date: Mon, 13 Jul 2015 15:20:21 -0400 Subject: [PATCH 084/165] Have lookup_user GET and not POST --- twython/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 60663cac..356cd8a2 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -458,7 +458,7 @@ def lookup_user(self, **params): Docs: https://dev.twitter.com/docs/api/1.1/get/users/lookup """ - return self.post('users/lookup', params=params) + return self.get('users/lookup', params=params) def show_user(self, **params): """Returns a variety of information about the user specified by the From 34db3a58adaa85ca5824323f0488777efbc4b4df Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Sat, 18 Jul 2015 10:11:04 -0400 Subject: [PATCH 085/165] Update README.rst [ci skip] --- README.rst | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 80bfa753..6f128626 100644 --- a/README.rst +++ b/README.rst @@ -2,14 +2,17 @@ Twython ======= -.. image:: https://badge.fury.io/py/twython.png - :target: http://badge.fury.io/py/twython -.. image:: https://travis-ci.org/ryanmcgrath/twython.png?branch=master - :target: https://travis-ci.org/ryanmcgrath/twython -.. image:: https://pypip.in/d/twython/badge.png - :target: https://crate.io/packages/twython/ -.. image:: https://coveralls.io/repos/ryanmcgrath/twython/badge.png?branch=master - :target: https://coveralls.io/r/ryanmcgrath/twython?branch=master +.. image:: https://img.shields.io/pypi/v/twython.svg?style=flat-square + :target: https://pypi.python.org/pypi/twython + +.. image:: https://img.shields.io/pypi/dw/twython.svg?style=flat-square + :target: https://pypi.python.org/pypi/twython + +.. image:: https://img.shields.io/travis/ryanmcgrath/twython.svg?style=flat-square + :target: https://travis-ci.org/ryanmcgrath/twython + +.. image:: https://img.shields.io/coveralls/ryanmcgrath/twython/master.svg?style=flat-square + :target: https://coveralls.io/r/ryanmcgrath/twython?branch=master ``Twython`` is the premier Python library providing an easy (and up-to-date) way to access Twitter data. Actively maintained and featuring support for Python 2.6+ and Python 3. It's been battle tested by companies, educational institutions and individuals alike. Try it today! From faee7ea3ff28c3cbbeba08b6005f4c9bb8ec5fd8 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Sat, 18 Jul 2015 10:32:30 -0400 Subject: [PATCH 086/165] Update AUTHORS.rst, Update HISTORY.rst, Update version [ci skip] --- AUTHORS.rst | 8 ++++++++ HISTORY.rst | 5 ++++- docs/conf.py | 4 ++-- setup.py | 2 +- twython/__init__.py | 2 +- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 8a16050c..739e11fd 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -55,3 +55,11 @@ Patches and Suggestions - `Donne Martin `_, Fixed typos in `README.rst` - `Diego Allen `_, Add missing comma in documentation code snippet - `Ben McGinnes `_, Added mute API endpoints, a couple of examples, random bits. +- `Davis Silverman `_, Fixed endpoint HTTP method +- `David Beitey `_, Fixed documentation typos +- `7kfpun `_, Fixed documentation typos +- `Will Thompson `_, Fixed documentation typos +- `Andre Polykanine `_, Fixed documentation typos +- `Ben Bertka `_, Added dynamic filtering in streamer +- `Takahashi Shuuji `_, Fixed documentation typos +- `Jeremy Keen `_, Updated documentation example diff --git a/HISTORY.rst b/HISTORY.rst index a7c961d3..05f1f719 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,9 +3,12 @@ History ------- -3.3.0 (2014-xx-xx) +3.3.0 (2015-18-07) ++++++++++++++++++ - Added support for muting users +- Fix typos in documentation +- Updated documentation examples +- Added dynamic filtering to streamer 3.2.0 (2014-10-30) ++++++++++++++++++ diff --git a/docs/conf.py b/docs/conf.py index a0f9ef4f..480a355c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '3.2.0' +version = '3.3.0' # The full version, including alpha/beta/rc tags. -release = '3.2.0' +release = '3.3.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index e203cc98..6d05bafe 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.2.0' +__version__ = '3.3.0' packages = [ 'twython', diff --git a/twython/__init__.py b/twython/__init__.py index a79667d0..3525f3c7 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ """ __author__ = 'Ryan McGrath ' -__version__ = '3.2.0' +__version__ = '3.3.0' from .api import Twython from .streaming import TwythonStreamer From a875f270a83dd9003b577f6d9922cf32835e1514 Mon Sep 17 00:00:00 2001 From: Ethan Baer Date: Fri, 31 Jul 2015 15:03:26 -0500 Subject: [PATCH 087/165] fixed cursor function code to return next pages when funciton iter_mode is 'id' --- twython/api.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/twython/api.py b/twython/api.py index d2d5c03f..6046f131 100644 --- a/twython/api.py +++ b/twython/api.py @@ -18,6 +18,7 @@ from . import __version__ from .advisory import TwythonDeprecationWarning from .compat import json, urlencode, parse_qsl, quote_plus, str, is_py2 +from .compat import urlsplit from .endpoints import EndpointsMixin from .exceptions import TwythonError, TwythonAuthError, TwythonRateLimitError from .helpers import _transparent_params @@ -498,19 +499,27 @@ def cursor(self, function, return_pages=False, **params): try: if function.iter_mode == 'id': - if 'max_id' not in params: - # Add 1 to the id because since_id and - # max_id are inclusive - if hasattr(function, 'iter_metadata'): - since_id = content[function.iter_metadata].get('since_id_str') + # Set max_id in params to one less than lowest tweet id + if hasattr(function, 'iter_metadata'): + # Get supplied next max_id + metadata = content.get(function.iter_metadata) + if 'next_results' in metadata: + next_results = urlsplit(metadata['next_results']) + params = dict(parse_qsl(next_results.query)) else: - since_id = content[0]['id_str'] - params['since_id'] = (int(since_id) - 1) + # No more results + raise StopIteration + else: + # Twitter gives tweets in reverse chronological order: + params['max_id'] = str(int(content[-1]['id_str']) - 1) elif function.iter_mode == 'cursor': params['cursor'] = content['next_cursor_str'] except (TypeError, ValueError): # pragma: no cover raise TwythonError('Unable to generate next page of search \ results, `page` is not a number.') + except (KeyError, AttributeError): #pragma no cover + raise TwythonError('Unable to generate next page of search \ + results, content has unexpected structure.') @staticmethod def unicode2utf8(text): From 0b3df413d825af9026182a3fb96febde0a050a95 Mon Sep 17 00:00:00 2001 From: Ethan Baer Date: Fri, 31 Jul 2015 15:07:25 -0500 Subject: [PATCH 088/165] added urlsplit import for use in api.cursor function --- twython/compat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twython/compat.py b/twython/compat.py index c36b3269..7c049b00 100644 --- a/twython/compat.py +++ b/twython/compat.py @@ -25,7 +25,7 @@ if is_py2: from urllib import urlencode, quote_plus - from urlparse import parse_qsl + from urlparse import parse_qsl, urlsplit str = unicode basestring = basestring @@ -33,7 +33,7 @@ elif is_py3: - from urllib.parse import urlencode, quote_plus, parse_qsl + from urllib.parse import urlencode, quote_plus, parse_qsl, urlsplit str = str basestring = (str, bytes) From 4a362a42aaf2eeee4a2ccac8524afc19c107db69 Mon Sep 17 00:00:00 2001 From: Martey Dodoo Date: Thu, 13 Aug 2015 21:52:53 -0400 Subject: [PATCH 089/165] Update Cory Dolphin's Github username in AUTHORS. Cory Dolphin changed Github usernames from "wcdolphin" to "corydolphin". Github does not automatically redirect profile links when you change usernames so his link in the AUTHORS file went to a 404. https://help.github.com/articles/what-happens-when-i-change-my-username/#changes-that-arent-automatic --- AUTHORS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 739e11fd..6b7d14e0 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -44,7 +44,7 @@ Patches and Suggestions - `Oleg Anashkin `_, streaming ``handlers`` functionality - `Luis Alberto Santana `_, Fixed issue where Twython was unnecessarily disabling compression -- `Cory Dolphin `_, Added retry_after attribute to TwythonRateLimitError +- `Cory Dolphin `_, Added retry_after attribute to TwythonRateLimitError - `Natan L `_, Fixed typo in documentation - `Cash Costello `_, Moved tests to use `responsoes`, fixed typos in documentation - `Joe Cabrera `_, PEP 8 contributions From 2ea45abb734af5a3de1e89a4ed2ea1a63e482160 Mon Sep 17 00:00:00 2001 From: ping Date: Sat, 15 Aug 2015 19:43:59 +0800 Subject: [PATCH 090/165] Fix html_for_tweet when a hashtag/mention is a substring of another --- twython/api.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/twython/api.py b/twython/api.py index d2d5c03f..d83c2da4 100644 --- a/twython/api.py +++ b/twython/api.py @@ -10,6 +10,7 @@ """ import warnings +import re import requests from requests.auth import HTTPBasicAuth @@ -550,19 +551,22 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False): entities = tweet['entities'] # Mentions - for entity in entities['user_mentions']: + for entity in sorted(entities['user_mentions'], + key=lambda mention: len(mention['screen_name']), reverse=True): start, end = entity['indices'][0], entity['indices'][1] mention_html = '@%(screen_name)s' - text = text.replace(tweet['text'][start:end], - mention_html % {'screen_name': entity['screen_name']}) + text = re.sub(r'(?)' + tweet['text'][start:end] + '(?!)', + mention_html % {'screen_name': entity['screen_name']}, text) # Hashtags - for entity in entities['hashtags']: + for entity in sorted(entities['hashtags'], + key=lambda hashtag: len(hashtag['text']), reverse=True): start, end = entity['indices'][0], entity['indices'][1] hashtag_html = '#%(hashtag)s' - text = text.replace(tweet['text'][start:end], hashtag_html % {'hashtag': entity['text']}) + text = re.sub(r'(?)' + tweet['text'][start:end] + '(?!)', + hashtag_html % {'hashtag': entity['text']}, text) # Urls for entity in entities['urls']: From 62d51c74314b7c95976bd83602092408a07580e9 Mon Sep 17 00:00:00 2001 From: ping Date: Mon, 17 Aug 2015 14:22:32 +0800 Subject: [PATCH 091/165] Add support for quoted_status in html_for_tweet() --- twython/api.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/twython/api.py b/twython/api.py index d2d5c03f..68019f7a 100644 --- a/twython/api.py +++ b/twython/api.py @@ -528,7 +528,7 @@ def encode(text): return str(text) @staticmethod - def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False): + def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_quoted_status=False): """Return HTML for a tweet (urls, mentions, hashtags replaced with links) :param tweet: Tweet object from received from Twitter API @@ -595,4 +595,16 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False): text = text.replace(tweet['text'][start:end], url_html % (entity['url'], shown_url)) + if expand_quoted_status and tweet['is_quote_status']: + quoted_status = tweet['quoted_status'] + text += '
%(quote)s' \ + '%(quote_user_name)s' \ + '@%(quote_user_screen_name)s' \ + '
' % \ + {'quote': Twython.html_for_tweet(quoted_status, use_display_url, use_expanded_url, False), + 'quote_tweet_link': 'https://twitter.com/%s/status/%s' % + (quoted_status['user']['screen_name'], quoted_status['id_str']), + 'quote_user_name': quoted_status['user']['name'], + 'quote_user_screen_name': quoted_status['user']['screen_name']} + return text From 2cca89507992a760e65833ba88f0cec7be8d4cb8 Mon Sep 17 00:00:00 2001 From: Karambir Singh Nain Date: Wed, 23 Sep 2015 19:00:45 +0530 Subject: [PATCH 092/165] Add video upload endpoint --- docs/usage/advanced_usage.rst | 16 +++++++++ twython/api.py | 5 ++- twython/endpoints.py | 65 +++++++++++++++++++++++++++++++++-- 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index d72b79f4..fac35c21 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -35,6 +35,22 @@ Documentation: * https://dev.twitter.com/rest/reference/post/statuses/update * https://dev.twitter.com/rest/reference/post/media/upload +Updating Status with Video +-------------------------- + +This uploads a video as a media object and associates it with a status update. + +.. code-block:: python + + video = open('/path/to/file/video.mp4', 'rb') + response = twitter.upload_video(media=video, media_type='video/mp4') + twitter.update_status(status='Checkout this cool video!', media_ids=[response['media_id']]) + +Documentation: + +* https://dev.twitter.com/rest/reference/post/statuses/update +* https://dev.twitter.com/rest/reference/post/media/upload + Posting a Status with an Editing Image -------------------------------------- diff --git a/twython/api.py b/twython/api.py index 21b37daa..3805e2f6 100644 --- a/twython/api.py +++ b/twython/api.py @@ -194,7 +194,10 @@ def _request(self, url, method='GET', params=None, api_call=None): retry_after=response.headers.get('X-Rate-Limit-Reset')) try: - content = response.json() + if response.status_code == 204: + content = response.content + else: + content = response.json() except ValueError: raise TwythonError('Response was not valid JSON. \ Unable to decode.') diff --git a/twython/endpoints.py b/twython/endpoints.py index 356cd8a2..561ec452 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -14,7 +14,12 @@ https://dev.twitter.com/docs/api/1.1 """ +import os import warnings +try: + from StringIO import StringIO +except ImportError: + from io import StringIO from .advisory import TwythonDeprecationWarning @@ -139,6 +144,62 @@ def upload_media(self, **params): """ return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) + def upload_video(self, media, media_type, size=None): + """Uploads video file to Twitter servers in chunks. The file will be available to be attached + to a status for 60 minutes. To attach to a update, pass a list of returned media ids + to the 'update_status' method using the 'media_ids' param. + + Upload happens in 3 stages: + - INIT call with size of media to be uploaded(in bytes). If this is more than 15mb, twitter will return error. + - APPEND calls each with media chunk. This returns a 204(No Content) if chunk is received. + - FINALIZE call to complete media upload. This returns media_id to be used with status update. + + Twitter media upload api expects each chunk to be not more than 5mb. We are sending chunk of 1mb each. + + Docs: + https://dev.twitter.com/rest/public/uploading-media#chunkedupload + """ + upload_url = 'https://upload.twitter.com/1.1/media/upload.json' + if not size: + media.seek(0, os.SEEK_END) + size = media.tell() + media.seek(0) + + # Stage 1: INIT call + params = { + 'command': 'INIT', + 'media_type': media_type, + 'total_bytes': size + } + response_init = self.post(upload_url, params=params) + media_id = response_init['media_id'] + + # Stage 2: APPEND calls with 1mb chunks + segment_index = 0 + while True: + data = media.read(1*1024*1024) + if not data: + break + media_chunk = StringIO() + media_chunk.write(data) + media_chunk.seek(0) + + params = { + 'command': 'APPEND', + 'media_id': media_id, + 'segment_index': segment_index, + 'media': media_chunk, + } + self.post(upload_url, params=params) + segment_index += 1 + + # Stage 3: FINALIZE call to complete upload + params = { + 'command': 'FINALIZE', + 'media_id': media_id + } + return self.post(upload_url, params=params) + def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded representation of a Tweet on third party sites. @@ -546,7 +607,7 @@ def list_mute_ids(self, **params): list_mute_ids.iter_key = 'ids' def create_mute(self, **params): - """Mutes the specified user, preventing their tweets appearing + """Mutes the specified user, preventing their tweets appearing in the authenticating user's timeline. Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/create @@ -555,7 +616,7 @@ def create_mute(self, **params): return self.post('mutes/users/create', params=params) def destroy_mute(self, **params): - """Un-mutes the user specified in the user or ID parameter for + """Un-mutes the user specified in the user or ID parameter for the authenticating user. Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/destroy From ff4655c314277d9c2c18bda56f07ac03d6972d76 Mon Sep 17 00:00:00 2001 From: ping Date: Thu, 8 Oct 2015 11:31:07 +0800 Subject: [PATCH 093/165] Fix html_for_tweet for pre-quote implementation tweet html --- twython/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/api.py b/twython/api.py index 3805e2f6..2ed50bc2 100644 --- a/twython/api.py +++ b/twython/api.py @@ -597,7 +597,7 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q text = text.replace(tweet['text'][start:end], url_html % (entity['url'], shown_url)) - if expand_quoted_status and tweet['is_quote_status']: + if expand_quoted_status and tweet.get('is_quote_status'): quoted_status = tweet['quoted_status'] text += '
%(quote)s' \ '%(quote_user_name)s' \ From 99e9cebdc38c6c0271148a6da70a1d74214f0e77 Mon Sep 17 00:00:00 2001 From: ping Date: Tue, 29 Mar 2016 10:44:04 +0800 Subject: [PATCH 094/165] Fix issue where is_quote_status is true but there is no quoted_status --- twython/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/api.py b/twython/api.py index 2ed50bc2..1d514eb3 100644 --- a/twython/api.py +++ b/twython/api.py @@ -597,7 +597,7 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q text = text.replace(tweet['text'][start:end], url_html % (entity['url'], shown_url)) - if expand_quoted_status and tweet.get('is_quote_status'): + if expand_quoted_status and tweet.get('is_quote_status') and tweet.get('quoted_status'): quoted_status = tweet['quoted_status'] text += '
%(quote)s' \ '%(quote_user_name)s' \ From a1640f4a170ad49a88b1b29a46a9dad474c33326 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Fri, 22 Apr 2016 14:22:32 +0100 Subject: [PATCH 095/165] Link $IBM-style symbols in tweets in html_for_tweet() Fixes #412 --- docs/usage/special_functions.rst | 5 +++-- tests/config.py | 2 ++ tests/test_core.py | 9 ++++++++- twython/api.py | 11 ++++++++++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/usage/special_functions.rst b/docs/usage/special_functions.rst index c89da384..97e674c5 100644 --- a/docs/usage/special_functions.rst +++ b/docs/usage/special_functions.rst @@ -72,11 +72,11 @@ The above code takes all the tweets from a specific users timeline, loops over t So: - http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71 + http://t.co/FCmXyI6VHd is #cool, lol! @mikehelmick shd #checkitout. Love, @__twython__ $IBM https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71 will be replaced with: - google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71 + google.com is #cool, lol! @mikehelmick shd #checkitout. Love, @__twython__ $IBM github.com pic.twitter.com/N6InAO4B71 .. note:: When converting the string to HTML we add a class to each HTML tag so that you can maninpulate the DOM later on. @@ -84,6 +84,7 @@ will be replaced with: - For media urls that are replaced we add ``class="twython-media"`` to the anchor tag - For user mentions that are replaced we add ``class="twython-mention"`` to the anchor tag - For hashtags that are replaced we add ``class="twython-hashtag"`` to the anchor tag +- For symbols that are replaced we add ``class="twython-symbol"`` to the anchor tag This function accepts two parameters: ``use_display_url`` and ``use_expanded_url`` By default, ``use_display_url`` is ``True``. Meaning the link displayed in the tweet text will appear as (ex. google.com, github.com) diff --git a/tests/config.py b/tests/config.py index 21baa69b..d520a7c4 100644 --- a/tests/config.py +++ b/tests/config.py @@ -28,3 +28,5 @@ test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71', u'in_reply_to_status_id': None, u'id': 349683012054683648, u'favorite_count': 0, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [{u'id': 29251354, u'indices': [45, 57], u'id_str': u'29251354', u'screen_name': u'mikehelmick', u'name': u'Mike Helmick'}, {u'id': 1431865928, u'indices': [81, 93], u'id_str': u'1431865928', u'screen_name': u'__twython__', u'name': u'Twython'}], u'hashtags': [{u'indices': [28, 33], u'text': u'cool'}, {u'indices': [62, 73], u'text': u'checkitout'}], u'urls': [{u'url': u'http://t.co/FCmXyI6VHd', u'indices': [0, 22], u'expanded_url': u'http://google.com', u'display_url': u'google.com'}, {u'url': u'https://t.co/67pwRvY6z9', u'indices': [94, 117], u'expanded_url': u'https://github.com', u'display_url': u'github.com'}], u'media': [{u'id': 537884378513162240, u'id_str': u'537884378513162240', u'indices': [118, 140], u'media_url': u'http://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'media_url_https': u'https://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'url': u'http://t.co/N6InAO4B71', u'display_url': u'pic.twitter.com/N6InAO4B71', u'expanded_url': u'http://twitter.com/pingofglitch/status/537884380060844032/photo/1', u'type': u'photo', u'sizes': {u'large': {u'w': 1024, u'h': 640, u'resize': u'fit'}, u'thumb': {u'w': 150, u'h': 150, u'resize': u'crop'}, u'medium': {u'w': 600, u'h': 375, u'resize': u'fit'}, u'small': {u'w': 340, u'h': 212, u'resize': u'fit'}}}]}, u'in_reply_to_screen_name': None, u'id_str': u'349683012054683648', u'retweet_count': 0, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 1431865928, u'verified': False, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'description': {u'urls': []}}, u'followers_count': 1, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'1431865928', u'profile_background_color': u'3D3D3D', u'listed_count': 0, u'profile_background_image_url_https': u'https://si0.twimg.com/images/themes/theme1/bg.png', u'utc_offset': None, u'statuses_count': 2, u'description': u'', u'friends_count': 1, u'location': u'', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'following': False, u'geo_enabled': False, u'profile_background_image_url': u'http://a0.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'__twython__', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Twython', u'notifications': False, u'url': None, u'created_at': u'Thu May 16 01:11:09 +0000 2013', u'contributors_enabled': False, u'time_zone': None, u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'lang': u'en', u'created_at': u'Wed Jun 26 00:18:21 +0000 2013', u'in_reply_to_status_id_str': None, u'place': None} test_tweet_html = 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71' + +test_tweet_symbols_object = {u'text': u'Some symbols: $AAPL and $PEP and $ANOTHER and $A.', u'contributors': None, u'geo': None, u'favorited': True, u'in_reply_to_user_id_str': None, u'user': {u'location': u'', u'id_str': u'2030131', u'protected': False, u'profile_background_tile': False, u'friends_count': 18, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'entities': {u'description': {u'urls': []}}, u'lang': u'en', u'listed_count': 5, u'default_profile_image': True, u'default_profile': False, u'statuses_count': 447, u'notifications': False, u'profile_background_color': u'9AE4E8', u'profile_sidebar_fill_color': u'E0FF92', u'profile_link_color': u'0000FF', u'profile_image_url_https': u'https://abs.twimg.com/sticky/default_profile_images/default_profile_5_normal.png', u'followers_count': 8, u'geo_enabled': True, u'following': True, u'has_extended_profile': False, u'profile_use_background_image': True, u'profile_text_color': u'000000', u'screen_name': u'philgyfordtest', u'contributors_enabled': False, u'verified': False, u'name': u'Phil Gyford Test', u'profile_sidebar_border_color': u'000000', u'utc_offset': 0, u'profile_image_url': u'http://abs.twimg.com/sticky/default_profile_images/default_profile_5_normal.png', u'id': 2030131, u'favourites_count': 0, u'time_zone': u'London', u'url': None, u'is_translation_enabled': False, u'is_translator': False, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'description': u'', u'created_at': u'Fri Mar 23 16:56:52 +0000 2007', u'follow_request_sent': False}, u'in_reply_to_user_id': None, u'retweeted': False, u'coordinates': None, u'place': None, u'in_reply_to_status_id': None, u'lang': u'en', u'in_reply_to_status_id_str': None, u'truncated': False, u'retweet_count': 0, u'is_quote_status': False, u'id': 662694880657989632, u'id_str': u'662694880657989632', u'in_reply_to_screen_name': None, u'favorite_count': 1, u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [{u'indices': [14, 19], u'text': u'AAPL'}, {u'indices': [24, 28], u'text': u'PEP'}, {u'indices': [46, 48], u'text': u'A'}], u'urls': []}, u'created_at': u'Fri Nov 06 18:15:46 +0000 2015', u'source': u'Tweetbot for Mac'} diff --git a/tests/test_core.py b/tests/test_core.py index 1aa0eea3..a7c27584 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,7 +1,7 @@ from twython import Twython, TwythonError, TwythonAuthError, TwythonRateLimitError from .config import ( - test_tweet_object, test_tweet_html, unittest + test_tweet_object, test_tweet_html, test_tweet_symbols_object, unittest ) import responses @@ -317,3 +317,10 @@ def test_html_for_tweet_short_url(self): # Make sure HTML doesn't contain the display OR expanded url self.assertTrue('http://google.com' not in tweet_text) self.assertTrue('google.com' not in tweet_text) + + def test_html_for_tweet_symbols(self): + tweet_text = self.api.html_for_tweet(test_tweet_symbols_object) + # Should only link symbols listed in entities: + self.assertTrue('$AAPL' in tweet_text) + self.assertTrue('$ANOTHER' not in tweet_text) + diff --git a/twython/api.py b/twython/api.py index 2ed50bc2..fc932834 100644 --- a/twython/api.py +++ b/twython/api.py @@ -528,7 +528,7 @@ def encode(text): @staticmethod def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_quoted_status=False): - """Return HTML for a tweet (urls, mentions, hashtags replaced with links) + """Return HTML for a tweet (urls, mentions, hashtags, symbols replaced with links) :param tweet: Tweet object from received from Twitter API :param use_display_url: Use display URL to represent link @@ -566,6 +566,15 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q text = re.sub(r'(?)' + tweet['text'][start:end] + '(?!)', hashtag_html % {'hashtag': entity['text']}, text) + # Symbols + for entity in sorted(entities['symbols'], + key=lambda symbol: len(symbol['text']), reverse=True): + start, end = entity['indices'][0], entity['indices'][1] + + symbol_html = '$%(symbol)s' + text = re.sub(r'(?)' + re.escape(tweet['text'][start:end]) + '(?!)', + symbol_html % {'symbol': entity['text']}, text) + # Urls for entity in entities['urls']: start, end = entity['indices'][0], entity['indices'][1] From 3f6700373f9b4bf6d0734912adf6386af4a22ff2 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Sat, 30 Apr 2016 05:22:26 -0400 Subject: [PATCH 096/165] 3.4.0 release, update version and history --- HISTORY.rst | 6 ++++++ docs/conf.py | 4 ++-- setup.py | 2 +- twython/__init__.py | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 05f1f719..f8b0b314 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,12 @@ History ------- +3.4.0 (2016-30-04) +++++++++++++++++++ +- Added `upload_video` endpoint +- Fix quoted status checks in `html_for_tweet` +- Fix `html_for_tweet` method response when hashtag/mention is a substring of another + 3.3.0 (2015-18-07) ++++++++++++++++++ - Added support for muting users diff --git a/docs/conf.py b/docs/conf.py index 480a355c..23044e32 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '3.3.0' +version = '3.4.0' # The full version, including alpha/beta/rc tags. -release = '3.3.0' +release = '3.4.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 6d05bafe..7b38b99e 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.3.0' +__version__ = '3.4.0' packages = [ 'twython', diff --git a/twython/__init__.py b/twython/__init__.py index 3525f3c7..1e57ceef 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ """ __author__ = 'Ryan McGrath ' -__version__ = '3.3.0' +__version__ = '3.4.0' from .api import Twython from .streaming import TwythonStreamer From 86f878aad86bd76b6d7a09cbc709572b7ce4d4c9 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Sat, 30 Apr 2016 05:31:13 -0400 Subject: [PATCH 097/165] Fixes #417, update ReadTheDocs TLD in README --- README.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index 6f128626..061d1b7c 100644 --- a/README.rst +++ b/README.rst @@ -3,16 +3,16 @@ Twython .. image:: https://img.shields.io/pypi/v/twython.svg?style=flat-square - :target: https://pypi.python.org/pypi/twython + :target: https://pypi.python.org/pypi/twython .. image:: https://img.shields.io/pypi/dw/twython.svg?style=flat-square - :target: https://pypi.python.org/pypi/twython - + :target: https://pypi.python.org/pypi/twython + .. image:: https://img.shields.io/travis/ryanmcgrath/twython.svg?style=flat-square - :target: https://travis-ci.org/ryanmcgrath/twython + :target: https://travis-ci.org/ryanmcgrath/twython .. image:: https://img.shields.io/coveralls/ryanmcgrath/twython/master.svg?style=flat-square - :target: https://coveralls.io/r/ryanmcgrath/twython?branch=master + :target: https://coveralls.io/r/ryanmcgrath/twython?branch=master ``Twython`` is the premier Python library providing an easy (and up-to-date) way to access Twitter data. Actively maintained and featuring support for Python 2.6+ and Python 3. It's been battle tested by companies, educational institutions and individuals alike. Try it today! @@ -62,7 +62,7 @@ Or, if you want the code that is currently on GitHub Documentation ------------- -Documentation is available at https://twython.readthedocs.org/en/latest/ +Documentation is available at https://twython.readthedocs.io/en/latest/ Starting Out ------------ @@ -142,7 +142,7 @@ Once you have the final user tokens, store them in a database for later use:: OAUTH_TOKEN = final_step['oauth_token'] OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] -For OAuth 2 (Application Only, read-only) authentication, see `our documentation `_ +For OAuth 2 (Application Only, read-only) authentication, see `our documentation `_ Dynamic Function Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -174,7 +174,7 @@ Documentation: https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline Updating Status ~~~~~~~~~~~~~~~ -This method makes use of dynamic arguments, `read more about them `_ +This method makes use of dynamic arguments, `read more about them `_ Documentation: https://dev.twitter.com/docs/api/1/post/statuses/update @@ -195,8 +195,8 @@ Searching Advanced Usage -------------- -- `Advanced Twython Usage `_ -- `Streaming with Twython `_ +- `Advanced Twython Usage `_ +- `Streaming with Twython `_ Notes From f91da7cadf9d3ddeefd7e01b99622ad84fd14e88 Mon Sep 17 00:00:00 2001 From: Gage Coprivnicar Date: Sun, 12 Jun 2016 16:42:25 -0600 Subject: [PATCH 098/165] added get_direct_message example added a get_direct_message example hope this is helpful. I spent a lot of time trying to figure it out and figured I could help others with the same problem --- get_direct_message example | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 get_direct_message example diff --git a/get_direct_message example b/get_direct_message example new file mode 100644 index 00000000..5a27a066 --- /dev/null +++ b/get_direct_message example @@ -0,0 +1,17 @@ +from twython import Twython, TwythonError +twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) + + +get_list = twitter.get_direct_messages() +#Returns All Twitter DM information which is a lot in a list format + +dm_dict = get_list[0] +#Sets get_list to a dictionary, the number in the list is the direct message retrieved +#That means that 0 is the most recent and n-1 is the last DM revieved. +#You can cycle through all the numbers and it will return the text and the sender id of each + +print dm_dict['text'] +#Gets the text from the dictionary + +print dm_dict['sender']['id'] +#Gets the ID of the sender From f7ddbcf414924ae31db4144bc5ffa16a000ca8ba Mon Sep 17 00:00:00 2001 From: ping Date: Wed, 22 Jun 2016 22:20:26 +0800 Subject: [PATCH 099/165] Use GET request if the media upload command is STATUS --- twython/endpoints.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/twython/endpoints.py b/twython/endpoints.py index 561ec452..1131d83d 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -142,6 +142,10 @@ def upload_media(self, **params): Docs: https://dev.twitter.com/rest/reference/post/media/upload """ + # https://dev.twitter.com/rest/reference/get/media/upload-status + if params and params.get('command', '') == 'STATUS': + return self.get('https://upload.twitter.com/1.1/media/upload.json', params=params) + return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) def upload_video(self, media, media_type, size=None): From 1b20f8f0f9eeeb398c89feb1787e541b21098741 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Wed, 17 Aug 2016 14:18:59 +0100 Subject: [PATCH 100/165] Fix upload_video "string argument detected, got 'bytes'" bug. Needed to use BytesIO instead of StringIO when uploading video to Twitter. Tested with python 2.6.9, 2.7.11, 3.3.6, 3.5.1. fixes #422 --- twython/endpoints.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 561ec452..9fe7734d 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -16,10 +16,11 @@ import os import warnings -try: - from StringIO import StringIO -except ImportError: - from io import StringIO +from io import BytesIO +#try: + #from StringIO import StringIO +#except ImportError: + #from io import StringIO from .advisory import TwythonDeprecationWarning @@ -180,7 +181,7 @@ def upload_video(self, media, media_type, size=None): data = media.read(1*1024*1024) if not data: break - media_chunk = StringIO() + media_chunk = BytesIO() media_chunk.write(data) media_chunk.seek(0) From 2c02b622a2cf8db5a2f060b3cb5672ff4e88d189 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Wed, 14 Sep 2016 15:35:51 -0700 Subject: [PATCH 101/165] added media_category and support for STATUS calls --- twython/endpoints.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 9fe7734d..8814feec 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -199,7 +199,28 @@ def upload_video(self, media, media_type, size=None): 'command': 'FINALIZE', 'media_id': media_id } - return self.post(upload_url, params=params) + response_finalize = self.post(upload_url, params=params) + + # Stage 4: STATUS call if still processing + params = { + 'command': 'STATUS', + 'media_id': media_id + } + + processing_state = response_finalize['processing_info'].get('state', None) + + if processing_state is not None: + while (processing_state == "pending" or processing_state == "in_progress") : + # get the secs to wait + check_after_secs = response_finalize['processing_info'].get('check_after_secs', None) + + if check_after_secs is not None: + time.sleep(check_after_secs) + response_finalize = self.get(upload_url, params=params) + # get new state after waiting + processing_state = response_finalize['processing_info'].get('state') + + return response_finalize def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded From c57c4bfc3488c0905de49dbcfdd06a121d55f4b8 Mon Sep 17 00:00:00 2001 From: ping Date: Tue, 20 Sep 2016 17:18:21 +0800 Subject: [PATCH 102/165] Update html_for_tweet to support extended tweets --- tests/config.py | 4 ++++ tests/test_core.py | 15 +++++++++++- twython/api.py | 57 +++++++++++++++++++++++++++++++++------------- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/tests/config.py b/tests/config.py index d520a7c4..607bdd88 100644 --- a/tests/config.py +++ b/tests/config.py @@ -30,3 +30,7 @@ test_tweet_html = 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71' test_tweet_symbols_object = {u'text': u'Some symbols: $AAPL and $PEP and $ANOTHER and $A.', u'contributors': None, u'geo': None, u'favorited': True, u'in_reply_to_user_id_str': None, u'user': {u'location': u'', u'id_str': u'2030131', u'protected': False, u'profile_background_tile': False, u'friends_count': 18, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'entities': {u'description': {u'urls': []}}, u'lang': u'en', u'listed_count': 5, u'default_profile_image': True, u'default_profile': False, u'statuses_count': 447, u'notifications': False, u'profile_background_color': u'9AE4E8', u'profile_sidebar_fill_color': u'E0FF92', u'profile_link_color': u'0000FF', u'profile_image_url_https': u'https://abs.twimg.com/sticky/default_profile_images/default_profile_5_normal.png', u'followers_count': 8, u'geo_enabled': True, u'following': True, u'has_extended_profile': False, u'profile_use_background_image': True, u'profile_text_color': u'000000', u'screen_name': u'philgyfordtest', u'contributors_enabled': False, u'verified': False, u'name': u'Phil Gyford Test', u'profile_sidebar_border_color': u'000000', u'utc_offset': 0, u'profile_image_url': u'http://abs.twimg.com/sticky/default_profile_images/default_profile_5_normal.png', u'id': 2030131, u'favourites_count': 0, u'time_zone': u'London', u'url': None, u'is_translation_enabled': False, u'is_translator': False, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'description': u'', u'created_at': u'Fri Mar 23 16:56:52 +0000 2007', u'follow_request_sent': False}, u'in_reply_to_user_id': None, u'retweeted': False, u'coordinates': None, u'place': None, u'in_reply_to_status_id': None, u'lang': u'en', u'in_reply_to_status_id_str': None, u'truncated': False, u'retweet_count': 0, u'is_quote_status': False, u'id': 662694880657989632, u'id_str': u'662694880657989632', u'in_reply_to_screen_name': None, u'favorite_count': 1, u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [{u'indices': [14, 19], u'text': u'AAPL'}, {u'indices': [24, 28], u'text': u'PEP'}, {u'indices': [46, 48], u'text': u'A'}], u'urls': []}, u'created_at': u'Fri Nov 06 18:15:46 +0000 2015', u'source': u'Tweetbot for Mac'} + +test_tweet_compat_object = {u'contributors': None, u'truncated': True, u'text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/SRmsuks2ru', u'indices': [117, 140], u'expanded_url': u'https://twitter.com/i/web/status/777915304261193728', u'display_url': u'twitter.com/i/web/status/7\u2026'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None} +test_tweet_extended_object = {u'full_text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", u'truncated': False, u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'contributors': None, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [], u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'id_str': u'777914712382058496', u'indices': [140, 163], u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'type': u'photo', u'id': 777914712382058496, u'display_url': u'pic.twitter.com/I9pUC0NdZC'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'display_text_range': [0, 139], u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None, u'extended_entities': {u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'display_url': u'pic.twitter.com/I9pUC0NdZC', u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'video_info': {u'aspect_ratio': [1, 1], u'variants': [{u'url': u'https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4', u'bitrate': 0, u'content_type': u'video/mp4'}]}, u'id_str': u'777914712382058496', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'indices': [140, 163], u'type': u'animated_gif', u'id': 777914712382058496, u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg'}]}} +test_tweet_extended_html = 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC' diff --git a/tests/test_core.py b/tests/test_core.py index a7c27584..c7cf2a20 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,7 +1,10 @@ +# -*- coding: utf-8 -*- from twython import Twython, TwythonError, TwythonAuthError, TwythonRateLimitError from .config import ( - test_tweet_object, test_tweet_html, test_tweet_symbols_object, unittest + test_tweet_object, test_tweet_html, test_tweet_symbols_object, + test_tweet_compat_object, test_tweet_extended_object, test_tweet_extended_html, + unittest ) import responses @@ -324,3 +327,13 @@ def test_html_for_tweet_symbols(self): self.assertTrue('$AAPL' in tweet_text) self.assertTrue('$ANOTHER' not in tweet_text) + def test_html_for_tweet_compatmode(self): + tweet_text = self.api.html_for_tweet(test_tweet_compat_object) + # link to compat web status link + self.assertTrue( + u'twitter.com/i/web/status/7…' in tweet_text) + + def test_html_for_tweet_extendedmode(self): + tweet_text = self.api.html_for_tweet(test_tweet_extended_object) + # full tweet rendered with suffix + self.assertEqual(test_tweet_extended_html, tweet_text) diff --git a/twython/api.py b/twython/api.py index ce11a68d..f806cea9 100644 --- a/twython/api.py +++ b/twython/api.py @@ -544,8 +544,18 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q if 'retweeted_status' in tweet: tweet = tweet['retweeted_status'] + if 'extended_tweet' in tweet: + tweet = tweet['extended_tweet'] + + orig_tweet_text = tweet.get('full_text') or tweet['text'] + + display_text_range = tweet.get('display_text_range') or [0, len(orig_tweet_text)] + display_text_start, display_text_end = display_text_range[0], display_text_range[1] + display_text = orig_tweet_text[display_text_start:display_text_end] + prefix_text = orig_tweet_text[0:display_text_start] + suffix_text = orig_tweet_text[display_text_end:len(orig_tweet_text)] + if 'entities' in tweet: - text = tweet['text'] entities = tweet['entities'] # Mentions @@ -553,9 +563,13 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q key=lambda mention: len(mention['screen_name']), reverse=True): start, end = entity['indices'][0], entity['indices'][1] - mention_html = '@%(screen_name)s' - text = re.sub(r'(?)' + tweet['text'][start:end] + '(?!)', - mention_html % {'screen_name': entity['screen_name']}, text) + mention_html = '@%(screen_name)s' % {'screen_name': entity['screen_name']} + sub_expr = r'(?)' + orig_tweet_text[start:end] + '(?!)' + if display_text_start <= start <= display_text_end: + display_text = re.sub(sub_expr, mention_html, display_text) + else: + prefix_text = re.sub(sub_expr, mention_html, prefix_text) # Hashtags for entity in sorted(entities['hashtags'], @@ -563,8 +577,8 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q start, end = entity['indices'][0], entity['indices'][1] hashtag_html = '#%(hashtag)s' - text = re.sub(r'(?)' + tweet['text'][start:end] + '(?!)', - hashtag_html % {'hashtag': entity['text']}, text) + display_text = re.sub(r'(?)' + orig_tweet_text[start:end] + '(?!)', + hashtag_html % {'hashtag': entity['text']}, display_text) # Symbols for entity in sorted(entities['symbols'], @@ -572,8 +586,8 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q start, end = entity['indices'][0], entity['indices'][1] symbol_html = '$%(symbol)s' - text = re.sub(r'(?)' + re.escape(tweet['text'][start:end]) + '(?!)', - symbol_html % {'symbol': entity['text']}, text) + display_text = re.sub(r'(?)' + re.escape(orig_tweet_text[start:end]) + r'\b(?!)', + symbol_html % {'symbol': entity['text']}, display_text) # Urls for entity in entities['urls']: @@ -586,9 +600,11 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q else: shown_url = entity['url'] - url_html = '%s' - text = text.replace(tweet['text'][start:end], - url_html % (entity['url'], shown_url)) + url_html = '%s' % (entity['url'], shown_url) + if display_text_start <= start <= display_text_end: + display_text = display_text.replace(orig_tweet_text[start:end], url_html) + else: + suffix_text = suffix_text.replace(orig_tweet_text[start:end], url_html) # Media if 'media' in entities: @@ -602,13 +618,17 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q else: shown_url = entity['url'] - url_html = '%s' - text = text.replace(tweet['text'][start:end], - url_html % (entity['url'], shown_url)) + url_html = '%s' % (entity['url'], shown_url) + if display_text_start <= start <= display_text_end: + # for compatibility with pre-extended tweets + display_text = display_text.replace(orig_tweet_text[start:end], url_html) + else: + suffix_text = suffix_text.replace(orig_tweet_text[start:end], url_html) + quote_text = '' if expand_quoted_status and tweet.get('is_quote_status') and tweet.get('quoted_status'): quoted_status = tweet['quoted_status'] - text += '
%(quote)s' \ + quote_text += '
%(quote)s' \ '%(quote_user_name)s' \ '@%(quote_user_screen_name)s' \ '
' % \ @@ -618,4 +638,9 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q 'quote_user_name': quoted_status['user']['name'], 'quote_user_screen_name': quoted_status['user']['screen_name']} - return text + return '%(prefix)s%(display)s%(suffix)s%(quote)s' % { + 'prefix': '%s' % prefix_text if prefix_text else '', + 'display': display_text, + 'suffix': '%s' % suffix_text if suffix_text else '', + 'quote': quote_text + } From 7401adfb640e50021cab7db2041f13a21ce6bad2 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Sun, 25 Sep 2016 18:03:26 -0700 Subject: [PATCH 103/165] - Double -> single quotes - Removed default value from .get() - Added a check_progress param to upload_video to allow users to decide when to check 'STATUS' calls --- twython/endpoints.py | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 8814feec..33d4b008 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -145,7 +145,7 @@ def upload_media(self, **params): """ return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) - def upload_video(self, media, media_type, size=None): + def upload_video(self, media, media_type, size=None, check_progress=False): """Uploads video file to Twitter servers in chunks. The file will be available to be attached to a status for 60 minutes. To attach to a update, pass a list of returned media ids to the 'update_status' method using the 'media_ids' param. @@ -199,28 +199,33 @@ def upload_video(self, media, media_type, size=None): 'command': 'FINALIZE', 'media_id': media_id } - response_finalize = self.post(upload_url, params=params) - # Stage 4: STATUS call if still processing - params = { - 'command': 'STATUS', - 'media_id': media_id - } - - processing_state = response_finalize['processing_info'].get('state', None) + # Only get the status if explicity asked to + # Default to False + if check_progress == True: + + response_finalize = self.post(upload_url, params=params) + + # Stage 4: STATUS call if still processing + params = { + 'command': 'STATUS', + 'media_id': media_id + } + + processing_state = response_finalize['processing_info'].get('state') - if processing_state is not None: - while (processing_state == "pending" or processing_state == "in_progress") : - # get the secs to wait - check_after_secs = response_finalize['processing_info'].get('check_after_secs', None) + if processing_state is not None: + while (processing_state == 'pending' or processing_state == 'in_progress') : + # get the secs to wait + check_after_secs = response_finalize['processing_info'].get('check_after_secs', None) - if check_after_secs is not None: - time.sleep(check_after_secs) - response_finalize = self.get(upload_url, params=params) - # get new state after waiting - processing_state = response_finalize['processing_info'].get('state') + if check_after_secs is not None: + time.sleep(check_after_secs) + response_finalize = self.get(upload_url, params=params) + # get new state after waiting + processing_state = response_finalize['processing_info'].get('state') - return response_finalize + return self.post(upload_url, param=params) def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded From e76a290166884cebfffaca30bb847b00b1b2b29e Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 26 Sep 2016 12:51:05 -0700 Subject: [PATCH 104/165] fixed styling issues --- twython/endpoints.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 33d4b008..24ddcb97 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -200,11 +200,11 @@ def upload_video(self, media, media_type, size=None, check_progress=False): 'media_id': media_id } + response = self.post(upload_url, params=params) + # Only get the status if explicity asked to # Default to False - if check_progress == True: - - response_finalize = self.post(upload_url, params=params) + if check_progress: # Stage 4: STATUS call if still processing params = { @@ -212,20 +212,20 @@ def upload_video(self, media, media_type, size=None, check_progress=False): 'media_id': media_id } - processing_state = response_finalize['processing_info'].get('state') + processing_state = response['processing_info'].get('state') if processing_state is not None: while (processing_state == 'pending' or processing_state == 'in_progress') : # get the secs to wait - check_after_secs = response_finalize['processing_info'].get('check_after_secs', None) + check_after_secs = response['processing_info'].get('check_after_secs') if check_after_secs is not None: time.sleep(check_after_secs) - response_finalize = self.get(upload_url, params=params) + response = self.get(upload_url, params=params) # get new state after waiting - processing_state = response_finalize['processing_info'].get('state') + processing_state = response['processing_info'].get('state') - return self.post(upload_url, param=params) + return response def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded From 469432bcf84793db1137aaa6c5b3a4f70f386ad9 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 26 Sep 2016 14:22:19 -0700 Subject: [PATCH 105/165] - added support for media_category param - added code to handle empty responses for STATUS calls --- twython/endpoints.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 24ddcb97..8919a711 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -17,6 +17,7 @@ import os import warnings from io import BytesIO +from time import sleep #try: #from StringIO import StringIO #except ImportError: @@ -145,7 +146,7 @@ def upload_media(self, **params): """ return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) - def upload_video(self, media, media_type, size=None, check_progress=False): + def upload_video(self, media, media_type, media_category=None, size=None, check_progress=False): """Uploads video file to Twitter servers in chunks. The file will be available to be attached to a status for 60 minutes. To attach to a update, pass a list of returned media ids to the 'update_status' method using the 'media_ids' param. @@ -170,7 +171,8 @@ def upload_video(self, media, media_type, size=None, check_progress=False): params = { 'command': 'INIT', 'media_type': media_type, - 'total_bytes': size + 'total_bytes': size, + 'media_category': media_category } response_init = self.post(upload_url, params=params) media_id = response_init['media_id'] @@ -211,19 +213,24 @@ def upload_video(self, media, media_type, size=None, check_progress=False): 'command': 'STATUS', 'media_id': media_id } - - processing_state = response['processing_info'].get('state') - if processing_state is not None: + # added code to handle if media_category is NOT set and check_progress=True + # the API will return a NoneType object in this case + try: + processing_state = response.get('processing_info').get('state') + except AttributeError: + return response + + if processing_state: while (processing_state == 'pending' or processing_state == 'in_progress') : # get the secs to wait - check_after_secs = response['processing_info'].get('check_after_secs') + check_after_secs = response.get('processing_info').get('check_after_secs') - if check_after_secs is not None: - time.sleep(check_after_secs) + if check_after_secs: + sleep(check_after_secs) response = self.get(upload_url, params=params) # get new state after waiting - processing_state = response['processing_info'].get('state') + processing_state = response.get('processing_info').get('state') return response From 574483d87021e8cdd0272e79f9654f1040634153 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Tue, 6 Jun 2017 10:17:58 -0400 Subject: [PATCH 106/165] 3.5.0 Release --- HISTORY.rst | 6 ++++++ docs/conf.py | 4 ++-- setup.py | 2 +- twython/__init__.py | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index f8b0b314..4b0f7522 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,12 @@ History ------- +3.5.0 (2017-06-06) +++++++++++++++++++ +- Added support for "symbols" in `Twython.html_for_tweet()` +- Added support for extended tweets in `Twython.html_for_tweet()` +- You can now check progress of video uploads to Twitter when using `Twython.upload_video()` + 3.4.0 (2016-30-04) ++++++++++++++++++ - Added `upload_video` endpoint diff --git a/docs/conf.py b/docs/conf.py index 23044e32..2a22092b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '3.4.0' +version = '3.5.0' # The full version, including alpha/beta/rc tags. -release = '3.4.0' +release = '3.5.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 7b38b99e..b31d7af8 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.4.0' +__version__ = '3.5.0' packages = [ 'twython', diff --git a/twython/__init__.py b/twython/__init__.py index 1e57ceef..7404b288 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ """ __author__ = 'Ryan McGrath ' -__version__ = '3.4.0' +__version__ = '3.5.0' from .api import Twython from .streaming import TwythonStreamer From 5a87fc7d842994c48d32da10dc817fb1a9af16d7 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Fri, 16 Jun 2017 09:16:22 -0400 Subject: [PATCH 107/165] Fixes #446, add Python 3 classifier to setup.py --- setup.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b31d7af8..1448fa89 100755 --- a/setup.py +++ b/setup.py @@ -41,6 +41,12 @@ 'License :: OSI Approved :: MIT License', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Communications :: Chat', - 'Topic :: Internet' + 'Topic :: Internet', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', ] ) From b366ab55c3412e78c8dcf49ab73f04937fd31e67 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 22 Aug 2017 13:49:40 +0100 Subject: [PATCH 108/165] Improve replacing of entities with links in html_for_tweet() I've re-written the parts of `html_for_tweet()` so that it handles all the replacements of URLs, mentions, symbols and hashtags better. Mainly to fix #447 but it should be a little more robust generally. Shamelessly cribbed from https://stackoverflow.com/a/25514650/250962 Passes all tests, but I haven't checked it beyond that. Fixes #447 --- tests/config.py | 3 ++ tests/test_core.py | 7 +++ twython/api.py | 128 ++++++++++++++++++++++++++------------------- 3 files changed, 85 insertions(+), 53 deletions(-) diff --git a/tests/config.py b/tests/config.py index 607bdd88..9e0aa15f 100644 --- a/tests/config.py +++ b/tests/config.py @@ -34,3 +34,6 @@ test_tweet_compat_object = {u'contributors': None, u'truncated': True, u'text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/SRmsuks2ru', u'indices': [117, 140], u'expanded_url': u'https://twitter.com/i/web/status/777915304261193728', u'display_url': u'twitter.com/i/web/status/7\u2026'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None} test_tweet_extended_object = {u'full_text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", u'truncated': False, u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'contributors': None, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [], u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'id_str': u'777914712382058496', u'indices': [140, 163], u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'type': u'photo', u'id': 777914712382058496, u'display_url': u'pic.twitter.com/I9pUC0NdZC'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'display_text_range': [0, 139], u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None, u'extended_entities': {u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'display_url': u'pic.twitter.com/I9pUC0NdZC', u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'video_info': {u'aspect_ratio': [1, 1], u'variants': [{u'url': u'https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4', u'bitrate': 0, u'content_type': u'video/mp4'}]}, u'id_str': u'777914712382058496', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'indices': [140, 163], u'type': u'animated_gif', u'id': 777914712382058496, u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg'}]}} test_tweet_extended_html = 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC' + +test_tweet_identical_urls = {u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [], u'urls': [{u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [42, 65], u'url': u'https://t.co/W0uArTMk9N'}, {u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [101, 124], u'url': u'https://t.co/W0uArTMk9N'}]}, u'full_text': u'Use Cases, Trials and Making 5G a Reality https://t.co/W0uArTMk9N #5G #innovation via @5GWorldSeries https://t.co/W0uArTMk9N'} + diff --git a/tests/test_core.py b/tests/test_core.py index c7cf2a20..45d856e4 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4,6 +4,7 @@ from .config import ( test_tweet_object, test_tweet_html, test_tweet_symbols_object, test_tweet_compat_object, test_tweet_extended_object, test_tweet_extended_html, + test_tweet_identical_urls, unittest ) @@ -321,6 +322,12 @@ def test_html_for_tweet_short_url(self): self.assertTrue('http://google.com' not in tweet_text) self.assertTrue('google.com' not in tweet_text) + def test_html_for_tweet_identical_urls(self): + """If the 'url's for different url entities are identical, they should link correctly.""" + tweet_text = self.api.html_for_tweet(test_tweet_identical_urls) + self.assertEqual(tweet_text, + u'Use Cases, Trials and Making 5G a Reality buff.ly/2sEhrgO #5G #innovation via @5GWorldSeries buff.ly/2sEhrgO') + def test_html_for_tweet_symbols(self): tweet_text = self.api.html_for_tweet(test_tweet_symbols_object) # Should only link symbols listed in entities: diff --git a/twython/api.py b/twython/api.py index f806cea9..954033f1 100644 --- a/twython/api.py +++ b/twython/api.py @@ -556,62 +556,78 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q suffix_text = orig_tweet_text[display_text_end:len(orig_tweet_text)] if 'entities' in tweet: - entities = tweet['entities'] + # We'll put all the bits of replacement HTML and their starts/ends + # in this list: + entities = [] # Mentions - for entity in sorted(entities['user_mentions'], - key=lambda mention: len(mention['screen_name']), reverse=True): - start, end = entity['indices'][0], entity['indices'][1] - - mention_html = '@%(screen_name)s' % {'screen_name': entity['screen_name']} - sub_expr = r'(?)' + orig_tweet_text[start:end] + '(?!)' - if display_text_start <= start <= display_text_end: - display_text = re.sub(sub_expr, mention_html, display_text) - else: - prefix_text = re.sub(sub_expr, mention_html, prefix_text) + if 'user_mentions' in tweet['entities']: + for entity in tweet['entities']['user_mentions']: + temp = {} + temp['start'] = entity['indices'][0] + temp['end'] = entity['indices'][1] + + mention_html = '@%(screen_name)s' % {'screen_name': entity['screen_name']} + + if display_text_start <= temp['start'] <= display_text_end: + temp['replacement'] = mention_html + entities.append(temp) + else: + prefix_text = re.sub(sub_expr, mention_html, prefix_text) # Hashtags - for entity in sorted(entities['hashtags'], - key=lambda hashtag: len(hashtag['text']), reverse=True): - start, end = entity['indices'][0], entity['indices'][1] + if 'hashtags' in tweet['entities']: + for entity in tweet['entities']['hashtags']: + temp = {} + temp['start'] = entity['indices'][0] + temp['end'] = entity['indices'][1] - hashtag_html = '#%(hashtag)s' - display_text = re.sub(r'(?)' + orig_tweet_text[start:end] + '(?!)', - hashtag_html % {'hashtag': entity['text']}, display_text) + url_html = '#%(hashtag)s' % {'hashtag': entity['text']} + + temp['replacement'] = url_html + entities.append(temp) # Symbols - for entity in sorted(entities['symbols'], - key=lambda symbol: len(symbol['text']), reverse=True): - start, end = entity['indices'][0], entity['indices'][1] - - symbol_html = '$%(symbol)s' - display_text = re.sub(r'(?)' + re.escape(orig_tweet_text[start:end]) + r'\b(?!)', - symbol_html % {'symbol': entity['text']}, display_text) - - # Urls - for entity in entities['urls']: - start, end = entity['indices'][0], entity['indices'][1] - if use_display_url and entity.get('display_url') \ - and not use_expanded_url: - shown_url = entity['display_url'] - elif use_expanded_url and entity.get('expanded_url'): - shown_url = entity['expanded_url'] - else: - shown_url = entity['url'] - - url_html = '%s' % (entity['url'], shown_url) - if display_text_start <= start <= display_text_end: - display_text = display_text.replace(orig_tweet_text[start:end], url_html) - else: - suffix_text = suffix_text.replace(orig_tweet_text[start:end], url_html) - - # Media - if 'media' in entities: - for entity in entities['media']: - start, end = entity['indices'][0], entity['indices'][1] - if use_display_url and entity.get('display_url') \ - and not use_expanded_url: + if 'symbols' in tweet['entities']: + for entity in tweet['entities']['symbols']: + temp = {} + temp['start'] = entity['indices'][0] + temp['end'] = entity['indices'][1] + + url_html = '$%(symbol)s' % {'symbol': entity['text']} + + temp['replacement'] = url_html + entities.append(temp) + + # URLs + if 'urls' in tweet['entities']: + for entity in tweet['entities']['urls']: + temp = {} + temp['start'] = entity['indices'][0] + temp['end'] = entity['indices'][1] + + if use_display_url and entity.get('display_url') and not use_expanded_url: + shown_url = entity['display_url'] + elif use_expanded_url and entity.get('expanded_url'): + shown_url = entity['expanded_url'] + else: + shown_url = entity['url'] + + url_html = '%s' % (entity['url'], shown_url) + + if display_text_start <= temp['start'] <= display_text_end: + temp['replacement'] = url_html + entities.append(temp) + else: + suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html) + + if 'media' in tweet['entities']: + for entity in tweet['entities']['media']: + temp = {} + temp['start'] = entity['indices'][0] + temp['end'] = entity['indices'][1] + + if use_display_url and entity.get('display_url') and not use_expanded_url: shown_url = entity['display_url'] elif use_expanded_url and entity.get('expanded_url'): shown_url = entity['expanded_url'] @@ -619,11 +635,17 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q shown_url = entity['url'] url_html = '%s' % (entity['url'], shown_url) - if display_text_start <= start <= display_text_end: - # for compatibility with pre-extended tweets - display_text = display_text.replace(orig_tweet_text[start:end], url_html) + + if display_text_start <= temp['start'] <= display_text_end: + temp['replacement'] = url_html + entities.append(temp) else: - suffix_text = suffix_text.replace(orig_tweet_text[start:end], url_html) + suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html) + + # Now do all the replacements, starting from the end, so that the + # start/end indices still work: + for entity in sorted(entities, key=lambda e: e['start'], reverse=True): + display_text = display_text[0:entity['start']] + entity['replacement'] + display_text[entity['end']:] quote_text = '' if expand_quoted_status and tweet.get('is_quote_status') and tweet.get('quoted_status'): From 6890802b2ae528557fdb1a528b4efd17470b5702 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 22 Aug 2017 13:55:22 +0100 Subject: [PATCH 109/165] Add test for missing `symbols` in entities If a tweet has no `symbols` in its `entities` then `html_for_tweet()` was failing. I'm not sure how common this is but, for example, tweets in a downloaded archive do not have `symbols` for some reason. The previous change (b366ab5) fixed this, but I'm adding a test for this case. --- tests/test_core.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 45d856e4..2a30df58 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -334,6 +334,18 @@ def test_html_for_tweet_symbols(self): self.assertTrue('$AAPL' in tweet_text) self.assertTrue('$ANOTHER' not in tweet_text) + def test_html_for_tweet_no_symbols(self): + """Should still work if tweet object has no symbols list""" + tweet = test_tweet_symbols_object + # Save a copy: + symbols = tweet['entities']['symbols'] + del tweet['entities']['symbols'] + tweet_text = self.api.html_for_tweet(tweet) + self.assertTrue('symbols: $AAPL and' in tweet_text) + self.assertTrue('and $ANOTHER and $A.' in tweet_text) + # Put the symbols back: + test_tweet_symbols_object['entities']['symbols'] = symbols + def test_html_for_tweet_compatmode(self): tweet_text = self.api.html_for_tweet(test_tweet_compat_object) # link to compat web status link From ede941cf1a259f927861d988bd4873f5cd4e5d65 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 23 Aug 2017 11:29:20 -0400 Subject: [PATCH 110/165] Version 3.6.0 and update travis python versions --- .travis.yml | 5 +++-- HISTORY.rst | 5 +++++ docs/conf.py | 4 ++-- setup.py | 5 ++--- twython/__init__.py | 2 +- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 91ebf1d7..8da6c439 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,8 @@ language: python python: - 2.6 - 2.7 - - 3.3 + - 3.5 + - 3.6 env: global: - secure: USjLDneiXlVvEjkUVqTt+LBi0XJ4QhkRcJzqVXA9gEau1NTjAkNTPmHjUbOygp0dkfoV0uWrZKCw6fL1g+HJgWl0vHeHzcNl4mUkA+OwkGFHgaeIhvUfnyyJA8P3Zm21XHC+ehzMpEFN5fVNNhREjnRj+CXMc0FgA6knwBRobu4= @@ -17,7 +18,7 @@ env: - TEST_LIST_SLUG=team - TEST_LIST_OWNER_SCREEN_NAME=twitterapi - ACCESS_TOKEN_B64=U2FsdGVkX18QdBhvMNshM4PGy04tU3HLwKP+nNSoNZHKsvGLjELcWEXN2LIu/T+yngX1vGONf9lo14ElnfS4k7sfhiru8phR4+rZuBVP3bDvC2A6fXJuhuLqNhBrWqg32WQewvxLWDWBoKmnvRHg5b74GHh+IN/12tU0cBF2HK8= -install: +install: - pip install -r requirements.txt - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi script: nosetests -v -w tests/ --logging-filter="twython" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing diff --git a/HISTORY.rst b/HISTORY.rst index 4b0f7522..4249539a 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,11 @@ History ------- +3.6.0 (2017-23-08) +++++++++++++++++++ +- Improve replacing of entities with links in `html_for_tweet()` +- Update classifiers for PyPI + 3.5.0 (2017-06-06) ++++++++++++++++++ - Added support for "symbols" in `Twython.html_for_tweet()` diff --git a/docs/conf.py b/docs/conf.py index 2a22092b..601d0660 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '3.5.0' +version = '3.6.0' # The full version, including alpha/beta/rc tags. -release = '3.5.0' +release = '3.6.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 1448fa89..40b05b7f 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.5.0' +__version__ = '3.6.0' packages = [ 'twython', @@ -45,8 +45,7 @@ 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', ] ) diff --git a/twython/__init__.py b/twython/__init__.py index 7404b288..84e9ee63 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ """ __author__ = 'Ryan McGrath ' -__version__ = '3.5.0' +__version__ = '3.6.0' from .api import Twython from .streaming import TwythonStreamer From c63ed8559e04b098f965aa94ad5f3644ffca5fad Mon Sep 17 00:00:00 2001 From: Clayton A Davis Date: Wed, 6 Sep 2017 18:30:32 -0400 Subject: [PATCH 111/165] Improve error handling for api.cursor --- twython/api.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/twython/api.py b/twython/api.py index 954033f1..5f0c50d9 100644 --- a/twython/api.py +++ b/twython/api.py @@ -470,6 +470,11 @@ def cursor(self, function, return_pages=False, **params): >>> print result """ + if not callable(function): + raise TypeError('.cursor() takes a Twython function as its first \ + argument. Did you provide the result of a \ + function call?') + if not hasattr(function, 'iter_mode'): raise TwythonError('Unable to create generator for Twython \ method "%s"' % function.__name__) From 6166e86807fdf474c6521ed3b818d2a28cbda687 Mon Sep 17 00:00:00 2001 From: Clayton A Davis Date: Mon, 11 Sep 2017 13:52:43 -0400 Subject: [PATCH 112/165] Add test for cursor creation --- tests/test_core.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 2a30df58..44adf95d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -356,3 +356,15 @@ def test_html_for_tweet_extendedmode(self): tweet_text = self.api.html_for_tweet(test_tweet_extended_object) # full tweet rendered with suffix self.assertEqual(test_tweet_extended_html, tweet_text) + + def test_cursor_requires_twython_function(self): + """Test that cursor() raises when called without a Twython function""" + def init_and_iterate_cursor(*args, **kwargs): + cursor = self.api.cursor(*args, **kwargs) + return next(cursor) + + non_function = object() + non_twython_function = lambda x: x + + self.assertRaises(TypeError, init_and_iterate_cursor, non_function) + self.assertRaises(TwythonError, init_and_iterate_cursor, non_twython_function) From 0ee9b76b5cb6fd86ce8b75287ca33579fd1d18b5 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 3 Oct 2017 19:12:02 +0100 Subject: [PATCH 113/165] Make html_for_tweet() link a replied-to username If a tweet was a reply, then when `html_for_tweet()` tried to turn the initial "@username" into a link, there was: > NameError: name 'sub_expr' is not defined This is now fixed, with a test to ensure the "@username" becomes a link. --- tests/config.py | 2 ++ tests/test_core.py | 10 ++++++++-- twython/api.py | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/config.py b/tests/config.py index 9e0aa15f..c3ea809d 100644 --- a/tests/config.py +++ b/tests/config.py @@ -37,3 +37,5 @@ test_tweet_identical_urls = {u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [], u'urls': [{u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [42, 65], u'url': u'https://t.co/W0uArTMk9N'}, {u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [101, 124], u'url': u'https://t.co/W0uArTMk9N'}]}, u'full_text': u'Use Cases, Trials and Making 5G a Reality https://t.co/W0uArTMk9N #5G #innovation via @5GWorldSeries https://t.co/W0uArTMk9N'} +test_tweet_reply = { u'display_text_range': [12,114], u'in_reply_to_status_id_str':u'742374355531923456', u'source':u'web', u'geo':None, u'full_text':u'@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr', u'extended_entities':{ u'media':[] }, u'id_str':u'300', u'in_reply_to_status_id':742374355531923456, u'id':300, u'in_reply_to_screen_name':u'philgyford', u'retweet_count':0, u'user':{ }, u'created_at':u'Mon Jun 13 15:48:06 +0000 2016', u'lang':u'en', u'favorite_count':0, u'coordinates':None, u'place':None, u'contributors':None, u'in_reply_to_user_id':12552, u'in_reply_to_user_id_str':u'12552', u'retweeted':False, u'favorited':False, u'truncated':False, u'entities':{ u'user_mentions':[ { u'id_str':u'12552', u'id':12552, u'screen_name':u'philgyford', u'name':u'Phil Gyford', u'indices':[ 0, 11 ] } ], u'media':[ ], u'hashtags':[ ], u'symbols':[ ], u'urls':[ ] }, u'is_quote_status':False, u'possibly_sensitive':False } + diff --git a/tests/test_core.py b/tests/test_core.py index 44adf95d..b824f86c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3,8 +3,8 @@ from .config import ( test_tweet_object, test_tweet_html, test_tweet_symbols_object, - test_tweet_compat_object, test_tweet_extended_object, test_tweet_extended_html, - test_tweet_identical_urls, + test_tweet_compat_object, test_tweet_extended_object, + test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, unittest ) @@ -308,6 +308,12 @@ def test_html_for_tweet(self): tweet_text = self.api.html_for_tweet(test_tweet_object) self.assertEqual(test_tweet_html, tweet_text) + def test_html_for_tweet_reply(self): + """Test HTML for Tweet links the replied-to username.""" + tweet_text = self.api.html_for_tweet(test_tweet_reply) + self.assertEqual(tweet_text, + u'@philgyford Here’s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr') + def test_html_for_tweet_expanded_url(self): """Test using expanded url in HTML for Tweet displays full urls""" tweet_text = self.api.html_for_tweet(test_tweet_object, diff --git a/twython/api.py b/twython/api.py index 5f0c50d9..27821683 100644 --- a/twython/api.py +++ b/twython/api.py @@ -578,6 +578,9 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q temp['replacement'] = mention_html entities.append(temp) else: + # Make the '@username' at the start, before + # display_text, into a link: + sub_expr = r'(?)' + orig_tweet_text[temp['start']:temp['end']] + '(?!)' prefix_text = re.sub(sub_expr, mention_html, prefix_text) # Hashtags From 4f1e41a9e5e3cfc7814c6912f4213161e20c8d84 Mon Sep 17 00:00:00 2001 From: Clayton Davis Date: Fri, 6 Oct 2017 14:45:25 -0400 Subject: [PATCH 114/165] Update print statements to print() functions --- docs/usage/advanced_usage.rst | 2 +- docs/usage/special_functions.rst | 8 ++++---- docs/usage/streaming_api.rst | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index fac35c21..ce609a68 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -98,7 +98,7 @@ That being said, Twython offers a generator for search results and can be access results = twitter.cursor(twitter.search, q='python') for result in results: - print result + print(result) Manipulate the Request (headers, proxies, etc.) ----------------------------------------------- diff --git a/docs/usage/special_functions.rst b/docs/usage/special_functions.rst index 97e674c5..cde8f992 100644 --- a/docs/usage/special_functions.rst +++ b/docs/usage/special_functions.rst @@ -25,7 +25,7 @@ The Old Way results = twitter.search(q='twitter') if results.get('statuses'): for result in results['statuses']: - print result['id_str'] + print(result['id_str']) The New Way ^^^^^^^^^^^ @@ -39,7 +39,7 @@ The New Way results = twitter.cursor(twitter.search, q='twitter') for result in results: - print result['id_str'] + print(result['id_str']) Another example: @@ -47,7 +47,7 @@ Another example: results = twitter.cursor(t.get_mentions_timeline) for result in results: - print result['id_str'] + print(result['id_str']) HTML for Tweet @@ -66,7 +66,7 @@ This function takes a tweet object received from the Twitter API and returns an include_rts=True) for tweet in user_tweets: tweet['text'] = Twython.html_for_tweet(tweet) - print tweet['text'] + print(tweet['text']) The above code takes all the tweets from a specific users timeline, loops over them and replaces the value of ``tweet['text']`` with HTML. diff --git a/docs/usage/streaming_api.rst b/docs/usage/streaming_api.rst index 8b21c700..4d6c1c34 100644 --- a/docs/usage/streaming_api.rst +++ b/docs/usage/streaming_api.rst @@ -27,10 +27,10 @@ Now set up how you want to handle the signals. class MyStreamer(TwythonStreamer): def on_success(self, data): if 'text' in data: - print data['text'].encode('utf-8') + print(data['text']) def on_error(self, status_code, data): - print status_code + print(status_code) # Want to stop trying to get data because of the error? # Uncomment the next line! From c086449818d53ed1f6e0eb3d6ef96946de2e6282 Mon Sep 17 00:00:00 2001 From: Clayton Davis Date: Fri, 6 Oct 2017 14:57:36 -0400 Subject: [PATCH 115/165] Update StringIO import for py3k --- docs/usage/advanced_usage.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index ce609a68..32334c01 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -62,7 +62,12 @@ with a status update. # Assume you are working with a JPEG from PIL import Image - from StringIO import StringIO + try: + # Python 3 + from io import StringIO + except ImportError: + # Python 2 + from StringIO import StringIO photo = Image.open('/path/to/file/image.jpg') From e87b80710db0f74af8508353611e05608cd1cd19 Mon Sep 17 00:00:00 2001 From: FoxMaSk Date: Sat, 7 Oct 2017 16:46:56 +0200 Subject: [PATCH 116/165] update the link to the doc of the twitter API --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 1aa13e0c..05d2838b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,7 +16,7 @@ Features - Twitter lists - Timelines - Direct Messages - - and anything found in `the Twitter API docs `_. + - and anything found in `the Twitter API docs `_. - Image Uploading: - Update user status with an image - Change user avatar From 748d28cc71f05a67407e36aa31c5de429d8ce8db Mon Sep 17 00:00:00 2001 From: Jose Manuel Delicado Date: Sat, 7 Oct 2017 18:26:21 +0200 Subject: [PATCH 117/165] twython/api.py: JSON error is not raised if the response content is empty and the status code is not 204. If params is not a dictionary, _transparent_params is not called --- twython/api.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/twython/api.py b/twython/api.py index 27821683..577d6a6f 100644 --- a/twython/api.py +++ b/twython/api.py @@ -140,7 +140,11 @@ def _request(self, url, method='GET', params=None, api_call=None): params = params or {} func = getattr(self.client, method) - params, files = _transparent_params(params) + if type(params) is dict: + params, files = _transparent_params(params) + else: + params = params + files = list() requests_args = {} for k, v in self.client_args.items(): @@ -192,15 +196,16 @@ def _request(self, url, method='GET', params=None, api_call=None): error_message, error_code=response.status_code, retry_after=response.headers.get('X-Rate-Limit-Reset')) - + content="" try: if response.status_code == 204: content = response.content else: content = response.json() except ValueError: - raise TwythonError('Response was not valid JSON. \ - Unable to decode.') + if response.content!="": + raise TwythonError('Response was not valid JSON. \ + Unable to decode.') return content From 6fc7b9e0386110eb05ed897d561004115407c508 Mon Sep 17 00:00:00 2001 From: Jose Manuel Delicado Date: Sat, 7 Oct 2017 18:29:19 +0200 Subject: [PATCH 118/165] Added create_metadata endpoint --- twython/endpoints.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/twython/endpoints.py b/twython/endpoints.py index 95e33d29..1c1d80c6 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -14,6 +14,7 @@ https://dev.twitter.com/docs/api/1.1 """ +import json import os import warnings from io import BytesIO @@ -150,6 +151,13 @@ def upload_media(self, **params): return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) + def create_metadata(self, **params): + """ Adds metadata to a media element, such as image descriptions for visually impaired. + Docs: https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create + """ + params = json.dumps(params) + return self.post("https://upload.twitter.com/1.1/media/metadata/create.json", params=params) + def upload_video(self, media, media_type, media_category=None, size=None, check_progress=False): """Uploads video file to Twitter servers in chunks. The file will be available to be attached to a status for 60 minutes. To attach to a update, pass a list of returned media ids From 1511ee7b4d983c27292f9cb6060c011366cc7ac1 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Sat, 7 Oct 2017 18:08:01 +0100 Subject: [PATCH 119/165] Split test_html_for_tweet() tests into their own file --- tests/test_core.py | 68 +-------------------------------- tests/test_html_for_tweet.py | 74 ++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 66 deletions(-) create mode 100644 tests/test_html_for_tweet.py diff --git a/tests/test_core.py b/tests/test_core.py index b824f86c..4c215d4c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,12 +1,7 @@ # -*- coding: utf-8 -*- from twython import Twython, TwythonError, TwythonAuthError, TwythonRateLimitError -from .config import ( - test_tweet_object, test_tweet_html, test_tweet_symbols_object, - test_tweet_compat_object, test_tweet_extended_object, - test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, - unittest -) +from .config import unittest import responses import requests @@ -303,66 +298,6 @@ def test_encode(self): """Test encoding UTF-8 works""" self.api.encode('Twython is awesome!') - def test_html_for_tweet(self): - """Test HTML for Tweet returns what we want""" - tweet_text = self.api.html_for_tweet(test_tweet_object) - self.assertEqual(test_tweet_html, tweet_text) - - def test_html_for_tweet_reply(self): - """Test HTML for Tweet links the replied-to username.""" - tweet_text = self.api.html_for_tweet(test_tweet_reply) - self.assertEqual(tweet_text, - u'@philgyford Here’s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr') - - def test_html_for_tweet_expanded_url(self): - """Test using expanded url in HTML for Tweet displays full urls""" - tweet_text = self.api.html_for_tweet(test_tweet_object, - use_expanded_url=True) - # Make sure full url is in HTML - self.assertTrue('http://google.com' in tweet_text) - - def test_html_for_tweet_short_url(self): - """Test using expanded url in HTML for Tweet displays full urls""" - tweet_text = self.api.html_for_tweet(test_tweet_object, False) - # Make sure HTML doesn't contain the display OR expanded url - self.assertTrue('http://google.com' not in tweet_text) - self.assertTrue('google.com' not in tweet_text) - - def test_html_for_tweet_identical_urls(self): - """If the 'url's for different url entities are identical, they should link correctly.""" - tweet_text = self.api.html_for_tweet(test_tweet_identical_urls) - self.assertEqual(tweet_text, - u'Use Cases, Trials and Making 5G a Reality buff.ly/2sEhrgO #5G #innovation via @5GWorldSeries buff.ly/2sEhrgO') - - def test_html_for_tweet_symbols(self): - tweet_text = self.api.html_for_tweet(test_tweet_symbols_object) - # Should only link symbols listed in entities: - self.assertTrue('$AAPL' in tweet_text) - self.assertTrue('$ANOTHER' not in tweet_text) - - def test_html_for_tweet_no_symbols(self): - """Should still work if tweet object has no symbols list""" - tweet = test_tweet_symbols_object - # Save a copy: - symbols = tweet['entities']['symbols'] - del tweet['entities']['symbols'] - tweet_text = self.api.html_for_tweet(tweet) - self.assertTrue('symbols: $AAPL and' in tweet_text) - self.assertTrue('and $ANOTHER and $A.' in tweet_text) - # Put the symbols back: - test_tweet_symbols_object['entities']['symbols'] = symbols - - def test_html_for_tweet_compatmode(self): - tweet_text = self.api.html_for_tweet(test_tweet_compat_object) - # link to compat web status link - self.assertTrue( - u'twitter.com/i/web/status/7…' in tweet_text) - - def test_html_for_tweet_extendedmode(self): - tweet_text = self.api.html_for_tweet(test_tweet_extended_object) - # full tweet rendered with suffix - self.assertEqual(test_tweet_extended_html, tweet_text) - def test_cursor_requires_twython_function(self): """Test that cursor() raises when called without a Twython function""" def init_and_iterate_cursor(*args, **kwargs): @@ -374,3 +309,4 @@ def init_and_iterate_cursor(*args, **kwargs): self.assertRaises(TypeError, init_and_iterate_cursor, non_function) self.assertRaises(TwythonError, init_and_iterate_cursor, non_twython_function) + diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py new file mode 100644 index 00000000..b8b5dc4d --- /dev/null +++ b/tests/test_html_for_tweet.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from twython import Twython, TwythonError + +from .config import ( + test_tweet_object, test_tweet_html, test_tweet_symbols_object, + test_tweet_compat_object, test_tweet_extended_object, + test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, + unittest +) + + +class TestHtmlForTweetTestCase(unittest.TestCase): + def setUp(self): + self.api = Twython('', '', '', '') + + def test_basic(self): + """Test HTML for Tweet returns what we want""" + tweet_text = self.api.html_for_tweet(test_tweet_object) + self.assertEqual(test_tweet_html, tweet_text) + + def test_reply(self): + """Test HTML for Tweet links the replied-to username.""" + tweet_text = self.api.html_for_tweet(test_tweet_reply) + self.assertEqual(tweet_text, + u'@philgyford Here’s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr') + + def test_expanded_url(self): + """Test using expanded url in HTML for Tweet displays full urls""" + tweet_text = self.api.html_for_tweet(test_tweet_object, + use_expanded_url=True) + # Make sure full url is in HTML + self.assertTrue('http://google.com' in tweet_text) + + def test_short_url(self): + """Test using expanded url in HTML for Tweet displays full urls""" + tweet_text = self.api.html_for_tweet(test_tweet_object, False) + # Make sure HTML doesn't contain the display OR expanded url + self.assertTrue('http://google.com' not in tweet_text) + self.assertTrue('google.com' not in tweet_text) + + def test_identical_urls(self): + """If the 'url's for different url entities are identical, they should link correctly.""" + tweet_text = self.api.html_for_tweet(test_tweet_identical_urls) + self.assertEqual(tweet_text, + u'Use Cases, Trials and Making 5G a Reality buff.ly/2sEhrgO #5G #innovation via @5GWorldSeries buff.ly/2sEhrgO') + + def test_symbols(self): + tweet_text = self.api.html_for_tweet(test_tweet_symbols_object) + # Should only link symbols listed in entities: + self.assertTrue('$AAPL' in tweet_text) + self.assertTrue('$ANOTHER' not in tweet_text) + + def test_no_symbols(self): + """Should still work if tweet object has no symbols list""" + tweet = test_tweet_symbols_object + # Save a copy: + symbols = tweet['entities']['symbols'] + del tweet['entities']['symbols'] + tweet_text = self.api.html_for_tweet(tweet) + self.assertTrue('symbols: $AAPL and' in tweet_text) + self.assertTrue('and $ANOTHER and $A.' in tweet_text) + # Put the symbols back: + test_tweet_symbols_object['entities']['symbols'] = symbols + + def test_compatmode(self): + tweet_text = self.api.html_for_tweet(test_tweet_compat_object) + # link to compat web status link + self.assertTrue( + u'twitter.com/i/web/status/7…' in tweet_text) + + def test_extendedmode(self): + tweet_text = self.api.html_for_tweet(test_tweet_extended_object) + # full tweet rendered with suffix + self.assertEqual(test_tweet_extended_html, tweet_text) From a27efd9da8b6eb7350d650c0fc14aca80c32b466 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Sat, 7 Oct 2017 18:38:20 +0100 Subject: [PATCH 120/165] Fix html_for_tweet()s handling of media URLs We were trying to link to each media item using its `url`/`expanded_url`. But there is only one of these, shared across all of a tweet's media items. So attempting to put it in several times, in the same location, was a bit of a mess! So it now only puts the `url`/`expanded_url` in once, no matter how many media items there are. --- tests/config.py | 1 + tests/test_html_for_tweet.py | 9 +++ tweet.json | 142 +++++++++++++++++++++++++++++++++++ twython/api.py | 44 ++++++----- 4 files changed, 176 insertions(+), 20 deletions(-) create mode 100644 tweet.json diff --git a/tests/config.py b/tests/config.py index c3ea809d..07ca96d6 100644 --- a/tests/config.py +++ b/tests/config.py @@ -39,3 +39,4 @@ test_tweet_reply = { u'display_text_range': [12,114], u'in_reply_to_status_id_str':u'742374355531923456', u'source':u'web', u'geo':None, u'full_text':u'@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr', u'extended_entities':{ u'media':[] }, u'id_str':u'300', u'in_reply_to_status_id':742374355531923456, u'id':300, u'in_reply_to_screen_name':u'philgyford', u'retweet_count':0, u'user':{ }, u'created_at':u'Mon Jun 13 15:48:06 +0000 2016', u'lang':u'en', u'favorite_count':0, u'coordinates':None, u'place':None, u'contributors':None, u'in_reply_to_user_id':12552, u'in_reply_to_user_id_str':u'12552', u'retweeted':False, u'favorited':False, u'truncated':False, u'entities':{ u'user_mentions':[ { u'id_str':u'12552', u'id':12552, u'screen_name':u'philgyford', u'name':u'Phil Gyford', u'indices':[ 0, 11 ] } ], u'media':[ ], u'hashtags':[ ], u'symbols':[ ], u'urls':[ ] }, u'is_quote_status':False, u'possibly_sensitive':False } +test_tweet_media = {'user': {'name': 'Phil Gyford', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', 'verified': False, 'screen_name': 'philgyford', 'id': 12552, 'id_str': '12552', 'protected': False}, 'geo': {}, 'id': 905105588279013377, 'text': 'I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg', 'entities': {'user_mentions': [], 'hashtags': [], 'media': [{'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'id': 905105571765944320, 'media_url': 'http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'sizes': [{'h': 256, 'w': 680, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105571765944320'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'id': 905105572529393668, 'media_url': 'http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'sizes': [{'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 271, 'w': 680, 'resize': 'fit'}, {'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 399, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105572529393668'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'id': 905105573255016448, 'media_url': 'http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'sizes': [{'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 195, 'w': 680, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 287, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105573255016448'}], 'urls': [{'display_url': 'gyford.com/phil/writing/2…', 'url': 'https://t.co/2yUmmn3TOc', 'indices': [107, 130], 'expanded_url': 'http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php'}]}, 'source': 'web', 'created_at': '2017-09-05 16:29:22 +0000', 'id_str': '905105588279013377'} diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index b8b5dc4d..dc440478 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -5,6 +5,7 @@ test_tweet_object, test_tweet_html, test_tweet_symbols_object, test_tweet_compat_object, test_tweet_extended_object, test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, + test_tweet_media, unittest ) @@ -72,3 +73,11 @@ def test_extendedmode(self): tweet_text = self.api.html_for_tweet(test_tweet_extended_object) # full tweet rendered with suffix self.assertEqual(test_tweet_extended_html, tweet_text) + + def test_media(self): + tweet_text = self.api.html_for_tweet(test_tweet_media) + + self.assertEqual( + """I made some D3.js charts showing the years covered by books in a series compared to their publishing dates gyford.com/phil/writing/2… pic.twitter.com/OwNc6uJklg""", + tweet_text) + diff --git a/tweet.json b/tweet.json new file mode 100644 index 00000000..5e61ed8b --- /dev/null +++ b/tweet.json @@ -0,0 +1,142 @@ +{ + "source":"web", + "entities":{ + "user_mentions":[ ], + "media":[ + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "indices":[ 131, 154 ], + "url":"https://t.co/OwNc6uJklg", + "media_url":"http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", + "id_str":"905105571765944320", + "id":905105571765944320, + "media_url_https":"https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", + "sizes":[ + { + "h":256, + "resize":"fit", + "w":680 + }, + { + "h":376, + "resize":"fit", + "w":1000 + }, + { + "h":150, + "resize":"crop", + "w":150 + }, + { + "h":376, + "resize":"fit", + "w":1000 + }, + { + "h":376, + "resize":"fit", + "w":1000 + } + ], + "display_url":"pic.twitter.com/OwNc6uJklg" + }, + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "indices":[ 131, 154 ], + "url":"https://t.co/OwNc6uJklg", + "media_url":"http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg", + "id_str":"905105572529393668", + "id":905105572529393668, + "media_url_https":"https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg", + "sizes":[ + { + "h":399, + "resize":"fit", + "w":1000 + }, + { + "h":271, + "resize":"fit", + "w":680 + }, + { + "h":399, + "resize":"fit", + "w":1000 + }, + { + "h":150, + "resize":"crop", + "w":150 + }, + { + "h":399, + "resize":"fit", + "w":1000 + } + ], + "display_url":"pic.twitter.com/OwNc6uJklg" + }, + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "indices":[ 131, 154 ], + "url":"https://t.co/OwNc6uJklg", + "media_url":"http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg", + "id_str":"905105573255016448", + "id":905105573255016448, + "media_url_https":"https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg", + "sizes":[ + { + "h":287, + "resize":"fit", + "w":1000 + }, + { + "h":195, + "resize":"fit", + "w":680 + }, + { + "h":150, + "resize":"crop", + "w":150 + }, + { + "h":287, + "resize":"fit", + "w":1000 + }, + { + "h":287, + "resize":"fit", + "w":1000 + } + ], + "display_url":"pic.twitter.com/OwNc6uJklg" + } + ], + "hashtags":[ ], + "urls":[ + { + "indices":[ 107, 130 ], + "url":"https://t.co/2yUmmn3TOc", + "expanded_url":"http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php", + "display_url":"gyford.com/phil/writing/2\u2026" + } + ] + }, + "geo":{ }, + "id_str":"905105588279013377", + "text":"I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg", + "id":905105588279013377, + "created_at":"2017-09-05 16:29:22 +0000", + "user":{ + "name":"Phil Gyford", + "screen_name":"philgyford", + "protected":false, + "id_str":"12552", + "profile_image_url_https":"https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg", + "id":12552, + "verified":false + } +} diff --git a/twython/api.py b/twython/api.py index 27821683..fac7928d 100644 --- a/twython/api.py +++ b/twython/api.py @@ -629,26 +629,30 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q else: suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html) - if 'media' in tweet['entities']: - for entity in tweet['entities']['media']: - temp = {} - temp['start'] = entity['indices'][0] - temp['end'] = entity['indices'][1] - - if use_display_url and entity.get('display_url') and not use_expanded_url: - shown_url = entity['display_url'] - elif use_expanded_url and entity.get('expanded_url'): - shown_url = entity['expanded_url'] - else: - shown_url = entity['url'] - - url_html = '%s' % (entity['url'], shown_url) - - if display_text_start <= temp['start'] <= display_text_end: - temp['replacement'] = url_html - entities.append(temp) - else: - suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html) + if 'media' in tweet['entities'] and len(tweet['entities']['media']) > 0: + # We just link to the overall URL for the tweet's media, + # rather than to each individual item. + # So, we get the URL from the first media item: + entity = tweet['entities']['media'][0] + + temp = {} + temp['start'] = entity['indices'][0] + temp['end'] = entity['indices'][1] + + if use_display_url and entity.get('display_url') and not use_expanded_url: + shown_url = entity['display_url'] + elif use_expanded_url and entity.get('expanded_url'): + shown_url = entity['expanded_url'] + else: + shown_url = entity['url'] + + url_html = '%s' % (entity['url'], shown_url) + + if display_text_start <= temp['start'] <= display_text_end: + temp['replacement'] = url_html + entities.append(temp) + else: + suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html) # Now do all the replacements, starting from the end, so that the # start/end indices still work: From 89755a8643805457aa874f2d7f34c3700e662609 Mon Sep 17 00:00:00 2001 From: FoxMaSk Date: Sun, 8 Oct 2017 08:57:04 +0200 Subject: [PATCH 121/165] update the link to the doc of the twitter API --- docs/usage/advanced_usage.rst | 10 +++++----- docs/usage/basic_usage.rst | 8 ++++---- docs/usage/starting_out.rst | 2 +- docs/usage/streaming_api.rst | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index 32334c01..bdb0b231 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -29,11 +29,11 @@ This uploads an image as a media object and associates it with a status update. photo = open('/path/to/file/image.jpg', 'rb') response = twitter.upload_media(media=photo) twitter.update_status(status='Checkout this cool image!', media_ids=[response['media_id']]) - + Documentation: -* https://dev.twitter.com/rest/reference/post/statuses/update -* https://dev.twitter.com/rest/reference/post/media/upload +* https://developer.twitter.com/en/docs/api-reference-index +* https://developer.twitter.com/en/docs/media/upload-media/overview Updating Status with Video -------------------------- @@ -48,8 +48,8 @@ This uploads a video as a media object and associates it with a status update. Documentation: -* https://dev.twitter.com/rest/reference/post/statuses/update -* https://dev.twitter.com/rest/reference/post/media/upload +* https://developer.twitter.com/en/docs/api-reference-index +* https://developer.twitter.com/en/docs/media/upload-media/overview Posting a Status with an Editing Image -------------------------------------- diff --git a/docs/usage/basic_usage.rst b/docs/usage/basic_usage.rst index 84ffb087..d7e32e3f 100644 --- a/docs/usage/basic_usage.rst +++ b/docs/usage/basic_usage.rst @@ -28,7 +28,7 @@ Create a Twython instance with your application keys and the users OAuth tokens User Information ^^^^^^^^^^^^^^^^ -Documentation: https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials +Documentation: https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials .. code-block:: python @@ -37,7 +37,7 @@ Documentation: https://dev.twitter.com/docs/api/1.1/get/account/verify_credentia Authenticated Users Home Timeline ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Documentation: https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline +Documentation: https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline .. code-block:: python @@ -48,7 +48,7 @@ Updating Status This method makes use of dynamic arguments, :ref:`read more about them ` -Documentation: https://dev.twitter.com/docs/api/1.1/post/statuses/update +Documentation: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update .. code-block:: python @@ -72,7 +72,7 @@ Searching .. note:: Searching can be done whether you're authenticated via OAuth 1 or OAuth 2 -Documentation: https://dev.twitter.com/docs/api/1.1/get/search/tweets +Documentation: https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets .. code-block:: python diff --git a/docs/usage/starting_out.rst b/docs/usage/starting_out.rst index 0a681acb..7bb3d94e 100644 --- a/docs/usage/starting_out.rst +++ b/docs/usage/starting_out.rst @@ -10,7 +10,7 @@ This section is going to help you understand creating a Twitter Application, aut Beginning --------- -First, you'll want to head over to https://dev.twitter.com/apps and register an application! +First, you'll want to head over to https://apps.twitter.com/ and register an application! After you register, grab your applications ``Consumer Key`` and ``Consumer Secret`` from the application details tab. diff --git a/docs/usage/streaming_api.rst b/docs/usage/streaming_api.rst index 4d6c1c34..97ef6eaa 100644 --- a/docs/usage/streaming_api.rst +++ b/docs/usage/streaming_api.rst @@ -5,7 +5,7 @@ Streaming API This section will cover how to use Twython and interact with the Twitter Streaming API. -Streaming Documentation: https://dev.twitter.com/docs/streaming-apis +Streaming Documentation: https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/streaming-message-types .. important:: The Streaming API requires that you have OAuth 1 authentication credentials. If you don't have credentials, head over to the :ref:`authentication section ` and find out how! From 2cb2ed4a31826349ad81e39a5c035a7eed98c6b1 Mon Sep 17 00:00:00 2001 From: Jose Manuel Delicado Date: Mon, 9 Oct 2017 18:11:24 +0200 Subject: [PATCH 122/165] Twython/api.py: suggested changes in review have been made for pull request 460 --- twython/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/twython/api.py b/twython/api.py index 577d6a6f..0ee942ca 100644 --- a/twython/api.py +++ b/twython/api.py @@ -140,7 +140,7 @@ def _request(self, url, method='GET', params=None, api_call=None): params = params or {} func = getattr(self.client, method) - if type(params) is dict: + if isinstance(params, dict): params, files = _transparent_params(params) else: params = params @@ -196,14 +196,14 @@ def _request(self, url, method='GET', params=None, api_call=None): error_message, error_code=response.status_code, retry_after=response.headers.get('X-Rate-Limit-Reset')) - content="" + content = '' try: if response.status_code == 204: content = response.content else: content = response.json() except ValueError: - if response.content!="": + if response.content != '': raise TwythonError('Response was not valid JSON. \ Unable to decode.') From 13fd0a868436a1b38b7422c04da1755d94e6e1a8 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 10 Oct 2017 11:57:03 +0100 Subject: [PATCH 123/165] Define encoding for tests config file --- tests/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/config.py b/tests/config.py index 07ca96d6..7fc4759c 100644 --- a/tests/config.py +++ b/tests/config.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import os import sys From 9ade0946b58c4243759a393e35f87bd499134e5f Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 10 Oct 2017 12:24:09 +0100 Subject: [PATCH 124/165] Add test for html_for_tweet() for quoted tweets --- tests/config.py | 5 +++++ tests/test_html_for_tweet.py | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/config.py b/tests/config.py index 7fc4759c..795ba07a 100644 --- a/tests/config.py +++ b/tests/config.py @@ -34,10 +34,15 @@ test_tweet_compat_object = {u'contributors': None, u'truncated': True, u'text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/SRmsuks2ru', u'indices': [117, 140], u'expanded_url': u'https://twitter.com/i/web/status/777915304261193728', u'display_url': u'twitter.com/i/web/status/7\u2026'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None} test_tweet_extended_object = {u'full_text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", u'truncated': False, u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'contributors': None, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [], u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'id_str': u'777914712382058496', u'indices': [140, 163], u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'type': u'photo', u'id': 777914712382058496, u'display_url': u'pic.twitter.com/I9pUC0NdZC'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'display_text_range': [0, 139], u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None, u'extended_entities': {u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'display_url': u'pic.twitter.com/I9pUC0NdZC', u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'video_info': {u'aspect_ratio': [1, 1], u'variants': [{u'url': u'https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4', u'bitrate': 0, u'content_type': u'video/mp4'}]}, u'id_str': u'777914712382058496', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'indices': [140, 163], u'type': u'animated_gif', u'id': 777914712382058496, u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg'}]}} + test_tweet_extended_html = 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC' test_tweet_identical_urls = {u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [], u'urls': [{u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [42, 65], u'url': u'https://t.co/W0uArTMk9N'}, {u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [101, 124], u'url': u'https://t.co/W0uArTMk9N'}]}, u'full_text': u'Use Cases, Trials and Making 5G a Reality https://t.co/W0uArTMk9N #5G #innovation via @5GWorldSeries https://t.co/W0uArTMk9N'} test_tweet_reply = { u'display_text_range': [12,114], u'in_reply_to_status_id_str':u'742374355531923456', u'source':u'web', u'geo':None, u'full_text':u'@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr', u'extended_entities':{ u'media':[] }, u'id_str':u'300', u'in_reply_to_status_id':742374355531923456, u'id':300, u'in_reply_to_screen_name':u'philgyford', u'retweet_count':0, u'user':{ }, u'created_at':u'Mon Jun 13 15:48:06 +0000 2016', u'lang':u'en', u'favorite_count':0, u'coordinates':None, u'place':None, u'contributors':None, u'in_reply_to_user_id':12552, u'in_reply_to_user_id_str':u'12552', u'retweeted':False, u'favorited':False, u'truncated':False, u'entities':{ u'user_mentions':[ { u'id_str':u'12552', u'id':12552, u'screen_name':u'philgyford', u'name':u'Phil Gyford', u'indices':[ 0, 11 ] } ], u'media':[ ], u'hashtags':[ ], u'symbols':[ ], u'urls':[ ] }, u'is_quote_status':False, u'possibly_sensitive':False } + test_tweet_media = {'user': {'name': 'Phil Gyford', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', 'verified': False, 'screen_name': 'philgyford', 'id': 12552, 'id_str': '12552', 'protected': False}, 'geo': {}, 'id': 905105588279013377, 'text': 'I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg', 'entities': {'user_mentions': [], 'hashtags': [], 'media': [{'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'id': 905105571765944320, 'media_url': 'http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'sizes': [{'h': 256, 'w': 680, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105571765944320'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'id': 905105572529393668, 'media_url': 'http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'sizes': [{'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 271, 'w': 680, 'resize': 'fit'}, {'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 399, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105572529393668'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'id': 905105573255016448, 'media_url': 'http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'sizes': [{'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 195, 'w': 680, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 287, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105573255016448'}], 'urls': [{'display_url': 'gyford.com/phil/writing/2…', 'url': 'https://t.co/2yUmmn3TOc', 'indices': [107, 130], 'expanded_url': 'http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php'}]}, 'source': 'web', 'created_at': '2017-09-05 16:29:22 +0000', 'id_str': '905105588279013377'} + +test_tweet_quoted = {u'contributors': None, u'truncated': False, u'text': u'Here\u2019s a quoted tweet. https://t.co/3neKzof0gT', u'is_quote_status': True, u'in_reply_to_status_id': None, u'id': 917706313785905157, u'favorite_count': 0, u'source': u'Tweetbot for Mac', u'quoted_status_id': 917699069916729344, u'retweeted': False, u'coordinates': None, u'quoted_status': {u'contributors': None, u'truncated': False, u'text': u'The quoted tweet text.', u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 917699069916729344, u'favorite_count': 1, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917699069916729344', u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': False, u'default_profile_image': False, u'id': 12552, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'translator_type': u'none', u'profile_text_color': u'000000', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', u'profile_sidebar_fill_color': u'EEEEEE', u'entities': {u'url': {u'urls': [{u'url': u'https://t.co/FsYzXrATit', u'indices': [0, 23], u'expanded_url': u'http://www.gyford.com', u'display_url': u'gyford.com'}]}, u'description': {u'urls': []}}, u'followers_count': 2615, u'profile_sidebar_border_color': u'FFFFFF', u'id_str': u'12552', u'profile_background_color': u'C0C0C0', u'listed_count': 130, u'is_translation_enabled': False, u'utc_offset': 3600, u'statuses_count': 19182, u'description': u'Revised and updated edition for 2004.', u'friends_count': 308, u'location': u'London, UK', u'profile_link_color': u'0000FF', u'profile_image_url': u'http://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', u'following': False, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'philgyford', u'lang': u'en-gb', u'profile_background_tile': False, u'favourites_count': 2203, u'name': u'Phil Gyford', u'notifications': False, u'url': u'https://t.co/FsYzXrATit', u'created_at': u'Wed Nov 15 16:55:59 +0000 2006', u'contributors_enabled': False, u'time_zone': u'London', u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'lang': u'ht', u'created_at': u'Tue Oct 10 10:31:22 +0000 2017', u'in_reply_to_status_id_str': None, u'place': None}, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/3neKzof0gT', u'indices': [23, 46], u'expanded_url': u'https://twitter.com/philgyford/status/917699069916729344', u'display_url': u'twitter.com/philgyford/sta\u2026'}]}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917706313785905157', u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 2030131, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'translator_type': u'none', u'profile_text_color': u'000000', u'profile_image_url_https': u'https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', u'profile_sidebar_fill_color': u'E0FF92', u'entities': {u'description': {u'urls': [{u'url': u'https://t.co/FsYzXrATit', u'indices': [17, 40], u'expanded_url': u'http://www.gyford.com', u'display_url': u'gyford.com'}]}}, u'followers_count': 12, u'profile_sidebar_border_color': u'000000', u'id_str': u'2030131', u'profile_background_color': u'9AE4E8', u'listed_count': 4, u'is_translation_enabled': False, u'utc_offset': 3600, u'statuses_count': 497, u'description': u'Testing #testing https://t.co/FsYzXrATit $IBM #test @philgyford', u'friends_count': 18, u'location': u'', u'profile_link_color': u'0000FF', u'profile_image_url': u'http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', u'following': True, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'philgyfordtest', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Phil Gyford Test', u'notifications': False, u'url': None, u'created_at': u'Fri Mar 23 16:56:52 +0000 2007', u'contributors_enabled': False, u'time_zone': u'London', u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Tue Oct 10 11:00:10 +0000 2017', u'quoted_status_id_str': u'917699069916729344', u'in_reply_to_status_id_str': None, u'place': None} + diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index dc440478..6f1ffea3 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -5,7 +5,7 @@ test_tweet_object, test_tweet_html, test_tweet_symbols_object, test_tweet_compat_object, test_tweet_extended_object, test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, - test_tweet_media, + test_tweet_media, test_tweet_quoted, unittest ) @@ -81,3 +81,12 @@ def test_media(self): """I made some D3.js charts showing the years covered by books in a series compared to their publishing dates gyford.com/phil/writing/2… pic.twitter.com/OwNc6uJklg""", tweet_text) + def test_quoted(self): + "With expand_quoted_status=True it should include a quoted tweet." + tweet_text = self.api.html_for_tweet(test_tweet_quoted, + expand_quoted_status=True) + + self.assertEqual( + u"""Here\u2019s a quoted tweet. twitter.com/philgyford/sta\u2026
The quoted tweet text.Phil Gyford@philgyford
""", + tweet_text) + From 9ccdb48248c6ed033da60fc740fcfd8b808a94a3 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 10 Oct 2017 12:31:57 +0100 Subject: [PATCH 125/165] Add test for html_for_tweet() for retweets --- tests/config.py | 2 ++ tests/test_html_for_tweet.py | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/config.py b/tests/config.py index 795ba07a..d4fc4718 100644 --- a/tests/config.py +++ b/tests/config.py @@ -46,3 +46,5 @@ test_tweet_quoted = {u'contributors': None, u'truncated': False, u'text': u'Here\u2019s a quoted tweet. https://t.co/3neKzof0gT', u'is_quote_status': True, u'in_reply_to_status_id': None, u'id': 917706313785905157, u'favorite_count': 0, u'source': u'Tweetbot for Mac', u'quoted_status_id': 917699069916729344, u'retweeted': False, u'coordinates': None, u'quoted_status': {u'contributors': None, u'truncated': False, u'text': u'The quoted tweet text.', u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 917699069916729344, u'favorite_count': 1, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917699069916729344', u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': False, u'default_profile_image': False, u'id': 12552, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'translator_type': u'none', u'profile_text_color': u'000000', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', u'profile_sidebar_fill_color': u'EEEEEE', u'entities': {u'url': {u'urls': [{u'url': u'https://t.co/FsYzXrATit', u'indices': [0, 23], u'expanded_url': u'http://www.gyford.com', u'display_url': u'gyford.com'}]}, u'description': {u'urls': []}}, u'followers_count': 2615, u'profile_sidebar_border_color': u'FFFFFF', u'id_str': u'12552', u'profile_background_color': u'C0C0C0', u'listed_count': 130, u'is_translation_enabled': False, u'utc_offset': 3600, u'statuses_count': 19182, u'description': u'Revised and updated edition for 2004.', u'friends_count': 308, u'location': u'London, UK', u'profile_link_color': u'0000FF', u'profile_image_url': u'http://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', u'following': False, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'philgyford', u'lang': u'en-gb', u'profile_background_tile': False, u'favourites_count': 2203, u'name': u'Phil Gyford', u'notifications': False, u'url': u'https://t.co/FsYzXrATit', u'created_at': u'Wed Nov 15 16:55:59 +0000 2006', u'contributors_enabled': False, u'time_zone': u'London', u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'lang': u'ht', u'created_at': u'Tue Oct 10 10:31:22 +0000 2017', u'in_reply_to_status_id_str': None, u'place': None}, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/3neKzof0gT', u'indices': [23, 46], u'expanded_url': u'https://twitter.com/philgyford/status/917699069916729344', u'display_url': u'twitter.com/philgyford/sta\u2026'}]}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917706313785905157', u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 2030131, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'translator_type': u'none', u'profile_text_color': u'000000', u'profile_image_url_https': u'https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', u'profile_sidebar_fill_color': u'E0FF92', u'entities': {u'description': {u'urls': [{u'url': u'https://t.co/FsYzXrATit', u'indices': [17, 40], u'expanded_url': u'http://www.gyford.com', u'display_url': u'gyford.com'}]}}, u'followers_count': 12, u'profile_sidebar_border_color': u'000000', u'id_str': u'2030131', u'profile_background_color': u'9AE4E8', u'listed_count': 4, u'is_translation_enabled': False, u'utc_offset': 3600, u'statuses_count': 497, u'description': u'Testing #testing https://t.co/FsYzXrATit $IBM #test @philgyford', u'friends_count': 18, u'location': u'', u'profile_link_color': u'0000FF', u'profile_image_url': u'http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', u'following': True, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'philgyfordtest', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Phil Gyford Test', u'notifications': False, u'url': None, u'created_at': u'Fri Mar 23 16:56:52 +0000 2007', u'contributors_enabled': False, u'time_zone': u'London', u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Tue Oct 10 11:00:10 +0000 2017', u'quoted_status_id_str': u'917699069916729344', u'in_reply_to_status_id_str': None, u'place': None} +test_tweet_retweet = {'coordinates': None, 'source': 'Tweetbot for Mac', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 0, 'user': {'has_extended_profile': False, 'following': True, 'url': None, 'profile_background_tile': False, 'geo_enabled': True, 'favourites_count': 0, 'lang': 'en', 'follow_request_sent': False, 'utc_offset': 3600, 'time_zone': 'London', 'profile_sidebar_border_color': '000000', 'verified': False, 'profile_use_background_image': True, 'name': 'Phil Gyford Test', 'profile_background_image_url_https': 'https://abs.twimg.com/images/themes/theme1/bg.png', 'default_profile_image': True, 'friends_count': 18, 'profile_image_url': 'http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', 'profile_image_url_https': 'https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', 'default_profile': False, 'screen_name': 'philgyfordtest', 'profile_link_color': '0000FF', 'profile_text_color': '000000', 'id_str': '2030131', 'profile_background_color': '9AE4E8', 'followers_count': 12, 'entities': {'description': {'urls': [{'indices': [17, 40], 'url': 'https://t.co/FsYzXrATit', 'expanded_url': 'http://www.gyford.com', 'display_url': 'gyford.com'}]}}, 'profile_background_image_url': 'http://abs.twimg.com/images/themes/theme1/bg.png', 'protected': False, 'notifications': False, 'description': 'Testing #testing https://t.co/FsYzXrATit $IBM #test @philgyford', 'is_translation_enabled': False, 'is_translator': False, 'translator_type': 'none', 'contributors_enabled': False, 'statuses_count': 498, 'listed_count': 4, 'created_at': 'Fri Mar 23 16:56:52 +0000 2007', 'profile_sidebar_fill_color': 'E0FF92', 'id': 2030131, 'location': ''}, 'favorited': False, 'retweeted_status': {'coordinates': None, 'source': 'Pepys\' Diary', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 21, 'user': {'has_extended_profile': False, 'following': True, 'url': 'https://t.co/b5ZyzWwQQA', 'profile_background_tile': False, 'geo_enabled': False, 'favourites_count': 1, 'lang': 'en', 'follow_request_sent': False, 'utc_offset': 3600, 'time_zone': 'London', 'profile_sidebar_border_color': 'CCCCCC', 'verified': False, 'profile_banner_url': 'https://pbs.twimg.com/profile_banners/14475268/1432652170', 'profile_use_background_image': False, 'name': 'Samuel Pepys', 'profile_background_image_url_https': 'https://abs.twimg.com/images/themes/theme1/bg.png', 'default_profile_image': False, 'friends_count': 14, 'profile_image_url': 'http://pbs.twimg.com/profile_images/629231884304695296/VZ-9FQ28_normal.jpg', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/629231884304695296/VZ-9FQ28_normal.jpg', 'default_profile': False, 'screen_name': 'samuelpepys', 'profile_link_color': '549355', 'profile_text_color': '333333', 'id_str': '14475268', 'profile_background_color': 'F1F4EB', 'followers_count': 55980, 'entities': {'description': {'urls': []}, 'url': {'urls': [{'indices': [0, 23], 'url': 'https://t.co/b5ZyzWwQQA', 'expanded_url': 'http://www.pepysdiary.com/', 'display_url': 'pepysdiary.com'}]}}, 'profile_background_image_url': 'http://abs.twimg.com/images/themes/theme1/bg.png', 'protected': False, 'notifications': False, 'description': 'The diaries of Samuel Pepys in real time, 1660-69. Currently tweeting the events of 1664. Run by @philgyford.', 'is_translation_enabled': False, 'is_translator': False, 'translator_type': 'none', 'contributors_enabled': False, 'statuses_count': 11060, 'listed_count': 1223, 'created_at': 'Tue Apr 22 14:39:20 +0000 2008', 'profile_sidebar_fill_color': 'E8E7DC', 'id': 14475268, 'location': 'London, UK'}, 'favorited': False, 'id': 917459832885653506, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917459832885653506', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Mon Oct 09 18:40:44 +0000 2017', 'is_quote_status': False, 'retweeted': False}, 'id': 917712989649801216, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917712989649801216', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [{'id_str': '14475268', 'indices': [3, 15], 'name': 'Samuel Pepys', 'id': 14475268, 'screen_name': 'samuelpepys'}], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'RT @samuelpepys: My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Tue Oct 10 11:26:41 +0000 2017', 'is_quote_status': False, 'retweeted': False} + diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index 6f1ffea3..9ba25c66 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -5,7 +5,7 @@ test_tweet_object, test_tweet_html, test_tweet_symbols_object, test_tweet_compat_object, test_tweet_extended_object, test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, - test_tweet_media, test_tweet_quoted, + test_tweet_media, test_tweet_quoted, test_tweet_retweet, unittest ) @@ -90,3 +90,11 @@ def test_quoted(self): u"""Here\u2019s a quoted tweet. twitter.com/philgyford/sta\u2026
The quoted tweet text.Phil Gyford@philgyford
""", tweet_text) + def test_retweet(self): + "With expand_quoted_status=True it should include a quoted tweet." + tweet_text = self.api.html_for_tweet(test_tweet_retweet) + + self.assertEqual( + u"""My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.""", + tweet_text) + From 5c55aa88449a7110da1e1c4fea130d9109f303a6 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 10 Oct 2017 12:41:00 +0100 Subject: [PATCH 126/165] Cut some un-needed data out of the test tweet objects --- tests/config.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/config.py b/tests/config.py index d4fc4718..4d76d1cb 100644 --- a/tests/config.py +++ b/tests/config.py @@ -28,11 +28,12 @@ 'twitterapi') test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71', u'in_reply_to_status_id': None, u'id': 349683012054683648, u'favorite_count': 0, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [{u'id': 29251354, u'indices': [45, 57], u'id_str': u'29251354', u'screen_name': u'mikehelmick', u'name': u'Mike Helmick'}, {u'id': 1431865928, u'indices': [81, 93], u'id_str': u'1431865928', u'screen_name': u'__twython__', u'name': u'Twython'}], u'hashtags': [{u'indices': [28, 33], u'text': u'cool'}, {u'indices': [62, 73], u'text': u'checkitout'}], u'urls': [{u'url': u'http://t.co/FCmXyI6VHd', u'indices': [0, 22], u'expanded_url': u'http://google.com', u'display_url': u'google.com'}, {u'url': u'https://t.co/67pwRvY6z9', u'indices': [94, 117], u'expanded_url': u'https://github.com', u'display_url': u'github.com'}], u'media': [{u'id': 537884378513162240, u'id_str': u'537884378513162240', u'indices': [118, 140], u'media_url': u'http://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'media_url_https': u'https://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'url': u'http://t.co/N6InAO4B71', u'display_url': u'pic.twitter.com/N6InAO4B71', u'expanded_url': u'http://twitter.com/pingofglitch/status/537884380060844032/photo/1', u'type': u'photo', u'sizes': {u'large': {u'w': 1024, u'h': 640, u'resize': u'fit'}, u'thumb': {u'w': 150, u'h': 150, u'resize': u'crop'}, u'medium': {u'w': 600, u'h': 375, u'resize': u'fit'}, u'small': {u'w': 340, u'h': 212, u'resize': u'fit'}}}]}, u'in_reply_to_screen_name': None, u'id_str': u'349683012054683648', u'retweet_count': 0, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 1431865928, u'verified': False, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'description': {u'urls': []}}, u'followers_count': 1, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'1431865928', u'profile_background_color': u'3D3D3D', u'listed_count': 0, u'profile_background_image_url_https': u'https://si0.twimg.com/images/themes/theme1/bg.png', u'utc_offset': None, u'statuses_count': 2, u'description': u'', u'friends_count': 1, u'location': u'', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'following': False, u'geo_enabled': False, u'profile_background_image_url': u'http://a0.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'__twython__', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Twython', u'notifications': False, u'url': None, u'created_at': u'Thu May 16 01:11:09 +0000 2013', u'contributors_enabled': False, u'time_zone': None, u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'lang': u'en', u'created_at': u'Wed Jun 26 00:18:21 +0000 2013', u'in_reply_to_status_id_str': None, u'place': None} + test_tweet_html = 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71' -test_tweet_symbols_object = {u'text': u'Some symbols: $AAPL and $PEP and $ANOTHER and $A.', u'contributors': None, u'geo': None, u'favorited': True, u'in_reply_to_user_id_str': None, u'user': {u'location': u'', u'id_str': u'2030131', u'protected': False, u'profile_background_tile': False, u'friends_count': 18, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'entities': {u'description': {u'urls': []}}, u'lang': u'en', u'listed_count': 5, u'default_profile_image': True, u'default_profile': False, u'statuses_count': 447, u'notifications': False, u'profile_background_color': u'9AE4E8', u'profile_sidebar_fill_color': u'E0FF92', u'profile_link_color': u'0000FF', u'profile_image_url_https': u'https://abs.twimg.com/sticky/default_profile_images/default_profile_5_normal.png', u'followers_count': 8, u'geo_enabled': True, u'following': True, u'has_extended_profile': False, u'profile_use_background_image': True, u'profile_text_color': u'000000', u'screen_name': u'philgyfordtest', u'contributors_enabled': False, u'verified': False, u'name': u'Phil Gyford Test', u'profile_sidebar_border_color': u'000000', u'utc_offset': 0, u'profile_image_url': u'http://abs.twimg.com/sticky/default_profile_images/default_profile_5_normal.png', u'id': 2030131, u'favourites_count': 0, u'time_zone': u'London', u'url': None, u'is_translation_enabled': False, u'is_translator': False, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'description': u'', u'created_at': u'Fri Mar 23 16:56:52 +0000 2007', u'follow_request_sent': False}, u'in_reply_to_user_id': None, u'retweeted': False, u'coordinates': None, u'place': None, u'in_reply_to_status_id': None, u'lang': u'en', u'in_reply_to_status_id_str': None, u'truncated': False, u'retweet_count': 0, u'is_quote_status': False, u'id': 662694880657989632, u'id_str': u'662694880657989632', u'in_reply_to_screen_name': None, u'favorite_count': 1, u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [{u'indices': [14, 19], u'text': u'AAPL'}, {u'indices': [24, 28], u'text': u'PEP'}, {u'indices': [46, 48], u'text': u'A'}], u'urls': []}, u'created_at': u'Fri Nov 06 18:15:46 +0000 2015', u'source': u'Tweetbot for Mac'} +test_tweet_symbols_object = {u'text': u'Some symbols: $AAPL and $PEP and $ANOTHER and $A.', u'contributors': None, u'geo': None, u'favorited': True, u'in_reply_to_user_id_str': None, u'user': {u'screen_name': u'philgyfordtest', u'name': u'Phil Gyford Test'}, u'in_reply_to_user_id': None, u'retweeted': False, u'coordinates': None, u'place': None, u'in_reply_to_status_id': None, u'lang': u'en', u'in_reply_to_status_id_str': None, u'truncated': False, u'retweet_count': 0, u'is_quote_status': False, u'id': 662694880657989632, u'id_str': u'662694880657989632', u'in_reply_to_screen_name': None, u'favorite_count': 1, u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [{u'indices': [14, 19], u'text': u'AAPL'}, {u'indices': [24, 28], u'text': u'PEP'}, {u'indices': [46, 48], u'text': u'A'}], u'urls': []}, u'created_at': u'Fri Nov 06 18:15:46 +0000 2015', u'source': u'web'} -test_tweet_compat_object = {u'contributors': None, u'truncated': True, u'text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/SRmsuks2ru', u'indices': [117, 140], u'expanded_url': u'https://twitter.com/i/web/status/777915304261193728', u'display_url': u'twitter.com/i/web/status/7\u2026'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None} +test_tweet_compat_object = {u'contributors': None, u'truncated': True, u'text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/SRmsuks2ru', u'indices': [117, 140], u'expanded_url': u'https://twitter.com/i/web/status/777915304261193728', u'display_url': u'twitter.com/i/web/status/7\u2026'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'name': u'Twitter', u'screen_name': u'twitter'}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None} test_tweet_extended_object = {u'full_text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", u'truncated': False, u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'contributors': None, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [], u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'id_str': u'777914712382058496', u'indices': [140, 163], u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'type': u'photo', u'id': 777914712382058496, u'display_url': u'pic.twitter.com/I9pUC0NdZC'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'display_text_range': [0, 139], u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None, u'extended_entities': {u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'display_url': u'pic.twitter.com/I9pUC0NdZC', u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'video_info': {u'aspect_ratio': [1, 1], u'variants': [{u'url': u'https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4', u'bitrate': 0, u'content_type': u'video/mp4'}]}, u'id_str': u'777914712382058496', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'indices': [140, 163], u'type': u'animated_gif', u'id': 777914712382058496, u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg'}]}} test_tweet_extended_html = 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC' @@ -42,9 +43,10 @@ test_tweet_reply = { u'display_text_range': [12,114], u'in_reply_to_status_id_str':u'742374355531923456', u'source':u'web', u'geo':None, u'full_text':u'@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr', u'extended_entities':{ u'media':[] }, u'id_str':u'300', u'in_reply_to_status_id':742374355531923456, u'id':300, u'in_reply_to_screen_name':u'philgyford', u'retweet_count':0, u'user':{ }, u'created_at':u'Mon Jun 13 15:48:06 +0000 2016', u'lang':u'en', u'favorite_count':0, u'coordinates':None, u'place':None, u'contributors':None, u'in_reply_to_user_id':12552, u'in_reply_to_user_id_str':u'12552', u'retweeted':False, u'favorited':False, u'truncated':False, u'entities':{ u'user_mentions':[ { u'id_str':u'12552', u'id':12552, u'screen_name':u'philgyford', u'name':u'Phil Gyford', u'indices':[ 0, 11 ] } ], u'media':[ ], u'hashtags':[ ], u'symbols':[ ], u'urls':[ ] }, u'is_quote_status':False, u'possibly_sensitive':False } -test_tweet_media = {'user': {'name': 'Phil Gyford', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', 'verified': False, 'screen_name': 'philgyford', 'id': 12552, 'id_str': '12552', 'protected': False}, 'geo': {}, 'id': 905105588279013377, 'text': 'I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg', 'entities': {'user_mentions': [], 'hashtags': [], 'media': [{'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'id': 905105571765944320, 'media_url': 'http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'sizes': [{'h': 256, 'w': 680, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105571765944320'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'id': 905105572529393668, 'media_url': 'http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'sizes': [{'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 271, 'w': 680, 'resize': 'fit'}, {'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 399, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105572529393668'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'id': 905105573255016448, 'media_url': 'http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'sizes': [{'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 195, 'w': 680, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 287, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105573255016448'}], 'urls': [{'display_url': 'gyford.com/phil/writing/2…', 'url': 'https://t.co/2yUmmn3TOc', 'indices': [107, 130], 'expanded_url': 'http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php'}]}, 'source': 'web', 'created_at': '2017-09-05 16:29:22 +0000', 'id_str': '905105588279013377'} +test_tweet_media = {'user': {'name': 'Phil Gyford', 'screen_name': 'philgyford'}, 'geo': {}, 'id': 905105588279013377, 'text': 'I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg', 'entities': {'user_mentions': [], 'hashtags': [], 'media': [{'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'id': 905105571765944320, 'media_url': 'http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'sizes': [{'h': 256, 'w': 680, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105571765944320'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'id': 905105572529393668, 'media_url': 'http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'sizes': [{'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 271, 'w': 680, 'resize': 'fit'}, {'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 399, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105572529393668'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'id': 905105573255016448, 'media_url': 'http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'sizes': [{'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 195, 'w': 680, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 287, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105573255016448'}], 'urls': [{'display_url': 'gyford.com/phil/writing/2…', 'url': 'https://t.co/2yUmmn3TOc', 'indices': [107, 130], 'expanded_url': 'http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php'}]}, 'source': 'web', 'created_at': '2017-09-05 16:29:22 +0000', 'id_str': '905105588279013377'} + +test_tweet_quoted = {u'contributors': None, u'truncated': False, u'text': u'Here\u2019s a quoted tweet. https://t.co/3neKzof0gT', u'is_quote_status': True, u'in_reply_to_status_id': None, u'id': 917706313785905157, u'favorite_count': 0, u'source': u'web', u'quoted_status_id': 917699069916729344, u'retweeted': False, u'coordinates': None, u'quoted_status': {u'contributors': None, u'truncated': False, u'text': u'The quoted tweet text.', u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 917699069916729344, u'favorite_count': 1, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917699069916729344', u'favorited': False, u'user': {u'screen_name': u'philgyford', u'name': u'Phil Gyford'}, u'geo': None, u'in_reply_to_user_id_str': None, u'lang': u'ht', u'created_at': u'Tue Oct 10 10:31:22 +0000 2017', u'in_reply_to_status_id_str': None, u'place': None}, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/3neKzof0gT', u'indices': [23, 46], u'expanded_url': u'https://twitter.com/philgyford/status/917699069916729344', u'display_url': u'twitter.com/philgyford/sta\u2026'}]}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917706313785905157', u'favorited': False, u'user': {u'screen_name': u'philgyfordtest', u'name': u'Phil Gyford Test'}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Tue Oct 10 11:00:10 +0000 2017', u'quoted_status_id_str': u'917699069916729344', u'in_reply_to_status_id_str': None, u'place': None} -test_tweet_quoted = {u'contributors': None, u'truncated': False, u'text': u'Here\u2019s a quoted tweet. https://t.co/3neKzof0gT', u'is_quote_status': True, u'in_reply_to_status_id': None, u'id': 917706313785905157, u'favorite_count': 0, u'source': u'Tweetbot for Mac', u'quoted_status_id': 917699069916729344, u'retweeted': False, u'coordinates': None, u'quoted_status': {u'contributors': None, u'truncated': False, u'text': u'The quoted tweet text.', u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 917699069916729344, u'favorite_count': 1, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917699069916729344', u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': False, u'default_profile_image': False, u'id': 12552, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'translator_type': u'none', u'profile_text_color': u'000000', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', u'profile_sidebar_fill_color': u'EEEEEE', u'entities': {u'url': {u'urls': [{u'url': u'https://t.co/FsYzXrATit', u'indices': [0, 23], u'expanded_url': u'http://www.gyford.com', u'display_url': u'gyford.com'}]}, u'description': {u'urls': []}}, u'followers_count': 2615, u'profile_sidebar_border_color': u'FFFFFF', u'id_str': u'12552', u'profile_background_color': u'C0C0C0', u'listed_count': 130, u'is_translation_enabled': False, u'utc_offset': 3600, u'statuses_count': 19182, u'description': u'Revised and updated edition for 2004.', u'friends_count': 308, u'location': u'London, UK', u'profile_link_color': u'0000FF', u'profile_image_url': u'http://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', u'following': False, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'philgyford', u'lang': u'en-gb', u'profile_background_tile': False, u'favourites_count': 2203, u'name': u'Phil Gyford', u'notifications': False, u'url': u'https://t.co/FsYzXrATit', u'created_at': u'Wed Nov 15 16:55:59 +0000 2006', u'contributors_enabled': False, u'time_zone': u'London', u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'lang': u'ht', u'created_at': u'Tue Oct 10 10:31:22 +0000 2017', u'in_reply_to_status_id_str': None, u'place': None}, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/3neKzof0gT', u'indices': [23, 46], u'expanded_url': u'https://twitter.com/philgyford/status/917699069916729344', u'display_url': u'twitter.com/philgyford/sta\u2026'}]}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917706313785905157', u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 2030131, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'translator_type': u'none', u'profile_text_color': u'000000', u'profile_image_url_https': u'https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', u'profile_sidebar_fill_color': u'E0FF92', u'entities': {u'description': {u'urls': [{u'url': u'https://t.co/FsYzXrATit', u'indices': [17, 40], u'expanded_url': u'http://www.gyford.com', u'display_url': u'gyford.com'}]}}, u'followers_count': 12, u'profile_sidebar_border_color': u'000000', u'id_str': u'2030131', u'profile_background_color': u'9AE4E8', u'listed_count': 4, u'is_translation_enabled': False, u'utc_offset': 3600, u'statuses_count': 497, u'description': u'Testing #testing https://t.co/FsYzXrATit $IBM #test @philgyford', u'friends_count': 18, u'location': u'', u'profile_link_color': u'0000FF', u'profile_image_url': u'http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', u'following': True, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'philgyfordtest', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Phil Gyford Test', u'notifications': False, u'url': None, u'created_at': u'Fri Mar 23 16:56:52 +0000 2007', u'contributors_enabled': False, u'time_zone': u'London', u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Tue Oct 10 11:00:10 +0000 2017', u'quoted_status_id_str': u'917699069916729344', u'in_reply_to_status_id_str': None, u'place': None} +test_tweet_retweet = {'coordinates': None, 'source': 'web', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 0, 'user': {'name': 'Phil Gyford Test', 'screen_name': 'philgyfordtest'}, 'favorited': False, 'retweeted_status': {'coordinates': None, 'source': 'web', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 21, 'user': {'name': 'Samuel Pepys', 'screen_name': 'samuelpepys'}, 'favorited': False, 'id': 917459832885653506, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917459832885653506', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Mon Oct 09 18:40:44 +0000 2017', 'is_quote_status': False, 'retweeted': False}, 'id': 917712989649801216, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917712989649801216', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [{'id_str': '14475268', 'indices': [3, 15], 'name': 'Samuel Pepys', 'id': 14475268, 'screen_name': 'samuelpepys'}], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'RT @samuelpepys: My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Tue Oct 10 11:26:41 +0000 2017', 'is_quote_status': False, 'retweeted': False} -test_tweet_retweet = {'coordinates': None, 'source': 'Tweetbot for Mac', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 0, 'user': {'has_extended_profile': False, 'following': True, 'url': None, 'profile_background_tile': False, 'geo_enabled': True, 'favourites_count': 0, 'lang': 'en', 'follow_request_sent': False, 'utc_offset': 3600, 'time_zone': 'London', 'profile_sidebar_border_color': '000000', 'verified': False, 'profile_use_background_image': True, 'name': 'Phil Gyford Test', 'profile_background_image_url_https': 'https://abs.twimg.com/images/themes/theme1/bg.png', 'default_profile_image': True, 'friends_count': 18, 'profile_image_url': 'http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', 'profile_image_url_https': 'https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', 'default_profile': False, 'screen_name': 'philgyfordtest', 'profile_link_color': '0000FF', 'profile_text_color': '000000', 'id_str': '2030131', 'profile_background_color': '9AE4E8', 'followers_count': 12, 'entities': {'description': {'urls': [{'indices': [17, 40], 'url': 'https://t.co/FsYzXrATit', 'expanded_url': 'http://www.gyford.com', 'display_url': 'gyford.com'}]}}, 'profile_background_image_url': 'http://abs.twimg.com/images/themes/theme1/bg.png', 'protected': False, 'notifications': False, 'description': 'Testing #testing https://t.co/FsYzXrATit $IBM #test @philgyford', 'is_translation_enabled': False, 'is_translator': False, 'translator_type': 'none', 'contributors_enabled': False, 'statuses_count': 498, 'listed_count': 4, 'created_at': 'Fri Mar 23 16:56:52 +0000 2007', 'profile_sidebar_fill_color': 'E0FF92', 'id': 2030131, 'location': ''}, 'favorited': False, 'retweeted_status': {'coordinates': None, 'source': 'Pepys\' Diary', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 21, 'user': {'has_extended_profile': False, 'following': True, 'url': 'https://t.co/b5ZyzWwQQA', 'profile_background_tile': False, 'geo_enabled': False, 'favourites_count': 1, 'lang': 'en', 'follow_request_sent': False, 'utc_offset': 3600, 'time_zone': 'London', 'profile_sidebar_border_color': 'CCCCCC', 'verified': False, 'profile_banner_url': 'https://pbs.twimg.com/profile_banners/14475268/1432652170', 'profile_use_background_image': False, 'name': 'Samuel Pepys', 'profile_background_image_url_https': 'https://abs.twimg.com/images/themes/theme1/bg.png', 'default_profile_image': False, 'friends_count': 14, 'profile_image_url': 'http://pbs.twimg.com/profile_images/629231884304695296/VZ-9FQ28_normal.jpg', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/629231884304695296/VZ-9FQ28_normal.jpg', 'default_profile': False, 'screen_name': 'samuelpepys', 'profile_link_color': '549355', 'profile_text_color': '333333', 'id_str': '14475268', 'profile_background_color': 'F1F4EB', 'followers_count': 55980, 'entities': {'description': {'urls': []}, 'url': {'urls': [{'indices': [0, 23], 'url': 'https://t.co/b5ZyzWwQQA', 'expanded_url': 'http://www.pepysdiary.com/', 'display_url': 'pepysdiary.com'}]}}, 'profile_background_image_url': 'http://abs.twimg.com/images/themes/theme1/bg.png', 'protected': False, 'notifications': False, 'description': 'The diaries of Samuel Pepys in real time, 1660-69. Currently tweeting the events of 1664. Run by @philgyford.', 'is_translation_enabled': False, 'is_translator': False, 'translator_type': 'none', 'contributors_enabled': False, 'statuses_count': 11060, 'listed_count': 1223, 'created_at': 'Tue Apr 22 14:39:20 +0000 2008', 'profile_sidebar_fill_color': 'E8E7DC', 'id': 14475268, 'location': 'London, UK'}, 'favorited': False, 'id': 917459832885653506, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917459832885653506', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Mon Oct 09 18:40:44 +0000 2017', 'is_quote_status': False, 'retweeted': False}, 'id': 917712989649801216, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917712989649801216', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [{'id_str': '14475268', 'indices': [3, 15], 'name': 'Samuel Pepys', 'id': 14475268, 'screen_name': 'samuelpepys'}], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'RT @samuelpepys: My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Tue Oct 10 11:26:41 +0000 2017', 'is_quote_status': False, 'retweeted': False} From 5a008e7e77eec0016af938d833827332ebfaedc7 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Wed, 11 Oct 2017 18:27:53 +0100 Subject: [PATCH 127/165] Move all the raw tweets for tests into their own JSON files Seems better to have the raw data as JSON, like it comes from the API, then load it into python objects for each test. --- tests/config.py | 23 ---- tests/test_endpoints.py | 2 +- tests/test_html_for_tweet.py | 62 ++++++---- tests/tweets/basic.json | 170 +++++++++++++++++++++++++ tests/tweets/compat.json | 51 ++++++++ tests/tweets/extended.json | 204 ++++++++++++++++++++++++++++++ tests/tweets/identical_urls.json | 34 +++++ tests/tweets/media.json | 205 +++++++++++++++++++++++++++++++ tests/tweets/quoted.json | 94 ++++++++++++++ tests/tweets/reply.json | 62 ++++++++++ tests/tweets/retweet.json | 91 ++++++++++++++ tests/tweets/symbols.json | 61 +++++++++ 12 files changed, 1010 insertions(+), 49 deletions(-) create mode 100644 tests/tweets/basic.json create mode 100644 tests/tweets/compat.json create mode 100644 tests/tweets/extended.json create mode 100644 tests/tweets/identical_urls.json create mode 100644 tests/tweets/media.json create mode 100644 tests/tweets/quoted.json create mode 100644 tests/tweets/reply.json create mode 100644 tests/tweets/retweet.json create mode 100644 tests/tweets/symbols.json diff --git a/tests/config.py b/tests/config.py index 4d76d1cb..8812b811 100644 --- a/tests/config.py +++ b/tests/config.py @@ -27,26 +27,3 @@ test_list_owner_screen_name = os.environ.get('TEST_LIST_OWNER_SCREEN_NAME', 'twitterapi') -test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71', u'in_reply_to_status_id': None, u'id': 349683012054683648, u'favorite_count': 0, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [{u'id': 29251354, u'indices': [45, 57], u'id_str': u'29251354', u'screen_name': u'mikehelmick', u'name': u'Mike Helmick'}, {u'id': 1431865928, u'indices': [81, 93], u'id_str': u'1431865928', u'screen_name': u'__twython__', u'name': u'Twython'}], u'hashtags': [{u'indices': [28, 33], u'text': u'cool'}, {u'indices': [62, 73], u'text': u'checkitout'}], u'urls': [{u'url': u'http://t.co/FCmXyI6VHd', u'indices': [0, 22], u'expanded_url': u'http://google.com', u'display_url': u'google.com'}, {u'url': u'https://t.co/67pwRvY6z9', u'indices': [94, 117], u'expanded_url': u'https://github.com', u'display_url': u'github.com'}], u'media': [{u'id': 537884378513162240, u'id_str': u'537884378513162240', u'indices': [118, 140], u'media_url': u'http://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'media_url_https': u'https://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'url': u'http://t.co/N6InAO4B71', u'display_url': u'pic.twitter.com/N6InAO4B71', u'expanded_url': u'http://twitter.com/pingofglitch/status/537884380060844032/photo/1', u'type': u'photo', u'sizes': {u'large': {u'w': 1024, u'h': 640, u'resize': u'fit'}, u'thumb': {u'w': 150, u'h': 150, u'resize': u'crop'}, u'medium': {u'w': 600, u'h': 375, u'resize': u'fit'}, u'small': {u'w': 340, u'h': 212, u'resize': u'fit'}}}]}, u'in_reply_to_screen_name': None, u'id_str': u'349683012054683648', u'retweet_count': 0, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 1431865928, u'verified': False, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'description': {u'urls': []}}, u'followers_count': 1, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'1431865928', u'profile_background_color': u'3D3D3D', u'listed_count': 0, u'profile_background_image_url_https': u'https://si0.twimg.com/images/themes/theme1/bg.png', u'utc_offset': None, u'statuses_count': 2, u'description': u'', u'friends_count': 1, u'location': u'', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'following': False, u'geo_enabled': False, u'profile_background_image_url': u'http://a0.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'__twython__', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Twython', u'notifications': False, u'url': None, u'created_at': u'Thu May 16 01:11:09 +0000 2013', u'contributors_enabled': False, u'time_zone': None, u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'lang': u'en', u'created_at': u'Wed Jun 26 00:18:21 +0000 2013', u'in_reply_to_status_id_str': None, u'place': None} - -test_tweet_html = 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71' - -test_tweet_symbols_object = {u'text': u'Some symbols: $AAPL and $PEP and $ANOTHER and $A.', u'contributors': None, u'geo': None, u'favorited': True, u'in_reply_to_user_id_str': None, u'user': {u'screen_name': u'philgyfordtest', u'name': u'Phil Gyford Test'}, u'in_reply_to_user_id': None, u'retweeted': False, u'coordinates': None, u'place': None, u'in_reply_to_status_id': None, u'lang': u'en', u'in_reply_to_status_id_str': None, u'truncated': False, u'retweet_count': 0, u'is_quote_status': False, u'id': 662694880657989632, u'id_str': u'662694880657989632', u'in_reply_to_screen_name': None, u'favorite_count': 1, u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [{u'indices': [14, 19], u'text': u'AAPL'}, {u'indices': [24, 28], u'text': u'PEP'}, {u'indices': [46, 48], u'text': u'A'}], u'urls': []}, u'created_at': u'Fri Nov 06 18:15:46 +0000 2015', u'source': u'web'} - -test_tweet_compat_object = {u'contributors': None, u'truncated': True, u'text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/SRmsuks2ru', u'indices': [117, 140], u'expanded_url': u'https://twitter.com/i/web/status/777915304261193728', u'display_url': u'twitter.com/i/web/status/7\u2026'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'name': u'Twitter', u'screen_name': u'twitter'}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None} -test_tweet_extended_object = {u'full_text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", u'truncated': False, u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'contributors': None, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [], u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'id_str': u'777914712382058496', u'indices': [140, 163], u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'type': u'photo', u'id': 777914712382058496, u'display_url': u'pic.twitter.com/I9pUC0NdZC'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'display_text_range': [0, 139], u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None, u'extended_entities': {u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'display_url': u'pic.twitter.com/I9pUC0NdZC', u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'video_info': {u'aspect_ratio': [1, 1], u'variants': [{u'url': u'https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4', u'bitrate': 0, u'content_type': u'video/mp4'}]}, u'id_str': u'777914712382058496', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'indices': [140, 163], u'type': u'animated_gif', u'id': 777914712382058496, u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg'}]}} - -test_tweet_extended_html = 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC' - -test_tweet_identical_urls = {u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [], u'urls': [{u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [42, 65], u'url': u'https://t.co/W0uArTMk9N'}, {u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [101, 124], u'url': u'https://t.co/W0uArTMk9N'}]}, u'full_text': u'Use Cases, Trials and Making 5G a Reality https://t.co/W0uArTMk9N #5G #innovation via @5GWorldSeries https://t.co/W0uArTMk9N'} - -test_tweet_reply = { u'display_text_range': [12,114], u'in_reply_to_status_id_str':u'742374355531923456', u'source':u'web', u'geo':None, u'full_text':u'@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr', u'extended_entities':{ u'media':[] }, u'id_str':u'300', u'in_reply_to_status_id':742374355531923456, u'id':300, u'in_reply_to_screen_name':u'philgyford', u'retweet_count':0, u'user':{ }, u'created_at':u'Mon Jun 13 15:48:06 +0000 2016', u'lang':u'en', u'favorite_count':0, u'coordinates':None, u'place':None, u'contributors':None, u'in_reply_to_user_id':12552, u'in_reply_to_user_id_str':u'12552', u'retweeted':False, u'favorited':False, u'truncated':False, u'entities':{ u'user_mentions':[ { u'id_str':u'12552', u'id':12552, u'screen_name':u'philgyford', u'name':u'Phil Gyford', u'indices':[ 0, 11 ] } ], u'media':[ ], u'hashtags':[ ], u'symbols':[ ], u'urls':[ ] }, u'is_quote_status':False, u'possibly_sensitive':False } - - -test_tweet_media = {'user': {'name': 'Phil Gyford', 'screen_name': 'philgyford'}, 'geo': {}, 'id': 905105588279013377, 'text': 'I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg', 'entities': {'user_mentions': [], 'hashtags': [], 'media': [{'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'id': 905105571765944320, 'media_url': 'http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'sizes': [{'h': 256, 'w': 680, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105571765944320'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'id': 905105572529393668, 'media_url': 'http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'sizes': [{'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 271, 'w': 680, 'resize': 'fit'}, {'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 399, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105572529393668'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'id': 905105573255016448, 'media_url': 'http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'sizes': [{'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 195, 'w': 680, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 287, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105573255016448'}], 'urls': [{'display_url': 'gyford.com/phil/writing/2…', 'url': 'https://t.co/2yUmmn3TOc', 'indices': [107, 130], 'expanded_url': 'http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php'}]}, 'source': 'web', 'created_at': '2017-09-05 16:29:22 +0000', 'id_str': '905105588279013377'} - -test_tweet_quoted = {u'contributors': None, u'truncated': False, u'text': u'Here\u2019s a quoted tweet. https://t.co/3neKzof0gT', u'is_quote_status': True, u'in_reply_to_status_id': None, u'id': 917706313785905157, u'favorite_count': 0, u'source': u'web', u'quoted_status_id': 917699069916729344, u'retweeted': False, u'coordinates': None, u'quoted_status': {u'contributors': None, u'truncated': False, u'text': u'The quoted tweet text.', u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 917699069916729344, u'favorite_count': 1, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917699069916729344', u'favorited': False, u'user': {u'screen_name': u'philgyford', u'name': u'Phil Gyford'}, u'geo': None, u'in_reply_to_user_id_str': None, u'lang': u'ht', u'created_at': u'Tue Oct 10 10:31:22 +0000 2017', u'in_reply_to_status_id_str': None, u'place': None}, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/3neKzof0gT', u'indices': [23, 46], u'expanded_url': u'https://twitter.com/philgyford/status/917699069916729344', u'display_url': u'twitter.com/philgyford/sta\u2026'}]}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917706313785905157', u'favorited': False, u'user': {u'screen_name': u'philgyfordtest', u'name': u'Phil Gyford Test'}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Tue Oct 10 11:00:10 +0000 2017', u'quoted_status_id_str': u'917699069916729344', u'in_reply_to_status_id_str': None, u'place': None} - -test_tweet_retweet = {'coordinates': None, 'source': 'web', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 0, 'user': {'name': 'Phil Gyford Test', 'screen_name': 'philgyfordtest'}, 'favorited': False, 'retweeted_status': {'coordinates': None, 'source': 'web', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 21, 'user': {'name': 'Samuel Pepys', 'screen_name': 'samuelpepys'}, 'favorited': False, 'id': 917459832885653506, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917459832885653506', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Mon Oct 09 18:40:44 +0000 2017', 'is_quote_status': False, 'retweeted': False}, 'id': 917712989649801216, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917712989649801216', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [{'id_str': '14475268', 'indices': [3, 15], 'name': 'Samuel Pepys', 'id': 14475268, 'screen_name': 'samuelpepys'}], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'RT @samuelpepys: My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Tue Oct 10 11:26:41 +0000 2017', 'is_quote_status': False, 'retweeted': False} - - diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index aa79998e..ecdd5537 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -4,7 +4,7 @@ app_key, app_secret, oauth_token, oauth_token_secret, protected_twitter_1, protected_twitter_2, screen_name, test_tweet_id, test_list_slug, test_list_owner_screen_name, - access_token, test_tweet_object, test_tweet_html, unittest + access_token, unittest ) import time diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index 9ba25c66..cc49121e 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -1,98 +1,110 @@ # -*- coding: utf-8 -*- +import json + from twython import Twython, TwythonError -from .config import ( - test_tweet_object, test_tweet_html, test_tweet_symbols_object, - test_tweet_compat_object, test_tweet_extended_object, - test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, - test_tweet_media, test_tweet_quoted, test_tweet_retweet, - unittest -) +from .config import unittest class TestHtmlForTweetTestCase(unittest.TestCase): def setUp(self): self.api = Twython('', '', '', '') + def load_tweet(self, name): + f = open('tests/tweets/%s.json' % name) + tweet = json.load(f) + f.close() + return tweet + def test_basic(self): """Test HTML for Tweet returns what we want""" - tweet_text = self.api.html_for_tweet(test_tweet_object) - self.assertEqual(test_tweet_html, tweet_text) + tweet_object = self.load_tweet('basic') + tweet_text = self.api.html_for_tweet(tweet_object) + self.assertEqual(tweet_text, + 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71') def test_reply(self): """Test HTML for Tweet links the replied-to username.""" - tweet_text = self.api.html_for_tweet(test_tweet_reply) + tweet_object = self.load_tweet('reply') + tweet_text = self.api.html_for_tweet(tweet_object) self.assertEqual(tweet_text, u'@philgyford Here’s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr') def test_expanded_url(self): """Test using expanded url in HTML for Tweet displays full urls""" - tweet_text = self.api.html_for_tweet(test_tweet_object, + tweet_object = self.load_tweet('basic') + tweet_text = self.api.html_for_tweet(tweet_object, use_expanded_url=True) # Make sure full url is in HTML self.assertTrue('http://google.com' in tweet_text) def test_short_url(self): """Test using expanded url in HTML for Tweet displays full urls""" - tweet_text = self.api.html_for_tweet(test_tweet_object, False) + tweet_object = self.load_tweet('basic') + tweet_text = self.api.html_for_tweet(tweet_object, False) # Make sure HTML doesn't contain the display OR expanded url self.assertTrue('http://google.com' not in tweet_text) self.assertTrue('google.com' not in tweet_text) def test_identical_urls(self): """If the 'url's for different url entities are identical, they should link correctly.""" - tweet_text = self.api.html_for_tweet(test_tweet_identical_urls) + tweet_object = self.load_tweet('identical_urls') + tweet_text = self.api.html_for_tweet(tweet_object) self.assertEqual(tweet_text, u'Use Cases, Trials and Making 5G a Reality buff.ly/2sEhrgO #5G #innovation via @5GWorldSeries buff.ly/2sEhrgO') def test_symbols(self): - tweet_text = self.api.html_for_tweet(test_tweet_symbols_object) + tweet_object = self.load_tweet('symbols') + tweet_text = self.api.html_for_tweet(tweet_object) # Should only link symbols listed in entities: self.assertTrue('$AAPL' in tweet_text) self.assertTrue('$ANOTHER' not in tweet_text) def test_no_symbols(self): """Should still work if tweet object has no symbols list""" - tweet = test_tweet_symbols_object + tweet = self.load_tweet('symbols') # Save a copy: symbols = tweet['entities']['symbols'] del tweet['entities']['symbols'] tweet_text = self.api.html_for_tweet(tweet) self.assertTrue('symbols: $AAPL and' in tweet_text) self.assertTrue('and $ANOTHER and $A.' in tweet_text) - # Put the symbols back: - test_tweet_symbols_object['entities']['symbols'] = symbols def test_compatmode(self): - tweet_text = self.api.html_for_tweet(test_tweet_compat_object) + tweet_object = self.load_tweet('compat') + tweet_text = self.api.html_for_tweet(tweet_object) # link to compat web status link self.assertTrue( u'twitter.com/i/web/status/7…' in tweet_text) def test_extendedmode(self): - tweet_text = self.api.html_for_tweet(test_tweet_extended_object) + tweet_object = self.load_tweet('extended') + tweet_text = self.api.html_for_tweet(tweet_object) # full tweet rendered with suffix - self.assertEqual(test_tweet_extended_html, tweet_text) + self.assertEqual(tweet_text, + 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC') def test_media(self): - tweet_text = self.api.html_for_tweet(test_tweet_media) + tweet_object = self.load_tweet('media') + tweet_text = self.api.html_for_tweet(tweet_object) self.assertEqual( - """I made some D3.js charts showing the years covered by books in a series compared to their publishing dates gyford.com/phil/writing/2… pic.twitter.com/OwNc6uJklg""", + u"""I made some D3.js charts showing the years covered by books in a series compared to their publishing dates gyford.com/phil/writing/2\u2026 pic.twitter.com/OwNc6uJklg""", tweet_text) def test_quoted(self): "With expand_quoted_status=True it should include a quoted tweet." - tweet_text = self.api.html_for_tweet(test_tweet_quoted, + tweet_object = self.load_tweet('quoted') + tweet_text = self.api.html_for_tweet(tweet_object, expand_quoted_status=True) - self.assertEqual( u"""Here\u2019s a quoted tweet. twitter.com/philgyford/sta\u2026
The quoted tweet text.Phil Gyford@philgyford
""", tweet_text) def test_retweet(self): "With expand_quoted_status=True it should include a quoted tweet." - tweet_text = self.api.html_for_tweet(test_tweet_retweet) + tweet_object = self.load_tweet('retweet') + tweet_text = self.api.html_for_tweet(tweet_object) self.assertEqual( u"""My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.""", diff --git a/tests/tweets/basic.json b/tests/tweets/basic.json new file mode 100644 index 00000000..7243cd8c --- /dev/null +++ b/tests/tweets/basic.json @@ -0,0 +1,170 @@ +{ + "contributors":null, + "truncated":false, + "text":"http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71", + "in_reply_to_status_id":null, + "id":349683012054683648, + "favorite_count":0, + "source":"Twitter Web Client", + "retweeted":false, + "coordinates":null, + "entities":{ + "symbols":[ + + ], + "user_mentions":[ + { + "id":29251354, + "indices":[ + 45, + 57 + ], + "id_str":"29251354", + "screen_name":"mikehelmick", + "name":"Mike Helmick" + }, + { + "id":1431865928, + "indices":[ + 81, + 93 + ], + "id_str":"1431865928", + "screen_name":"__twython__", + "name":"Twython" + } + ], + "hashtags":[ + { + "indices":[ + 28, + 33 + ], + "text":"cool" + }, + { + "indices":[ + 62, + 73 + ], + "text":"checkitout" + } + ], + "urls":[ + { + "url":"http://t.co/FCmXyI6VHd", + "indices":[ + 0, + 22 + ], + "expanded_url":"http://google.com", + "display_url":"google.com" + }, + { + "url":"https://t.co/67pwRvY6z9", + "indices":[ + 94, + 117 + ], + "expanded_url":"https://github.com", + "display_url":"github.com" + } + ], + "media":[ + { + "id":537884378513162240, + "id_str":"537884378513162240", + "indices":[ + 118, + 140 + ], + "media_url":"http://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg", + "media_url_https":"https://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg", + "url":"http://t.co/N6InAO4B71", + "display_url":"pic.twitter.com/N6InAO4B71", + "expanded_url":"http://twitter.com/pingofglitch/status/537884380060844032/photo/1", + "type":"photo", + "sizes":{ + "large":{ + "w":1024, + "h":640, + "resize":"fit" + }, + "thumb":{ + "w":150, + "h":150, + "resize":"crop" + }, + "medium":{ + "w":600, + "h":375, + "resize":"fit" + }, + "small":{ + "w":340, + "h":212, + "resize":"fit" + } + } + } + ] + }, + "in_reply_to_screen_name":null, + "id_str":"349683012054683648", + "retweet_count":0, + "in_reply_to_user_id":null, + "favorited":false, + "user":{ + "follow_request_sent":false, + "profile_use_background_image":true, + "default_profile_image":true, + "id":1431865928, + "verified":false, + "profile_text_color":"333333", + "profile_image_url_https":"https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png", + "profile_sidebar_fill_color":"DDEEF6", + "entities":{ + "description":{ + "urls":[ + + ] + } + }, + "followers_count":1, + "profile_sidebar_border_color":"C0DEED", + "id_str":"1431865928", + "profile_background_color":"3D3D3D", + "listed_count":0, + "profile_background_image_url_https":"https://si0.twimg.com/images/themes/theme1/bg.png", + "utc_offset":null, + "statuses_count":2, + "description":"", + "friends_count":1, + "location":"", + "profile_link_color":"0084B4", + "profile_image_url":"http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png", + "following":false, + "geo_enabled":false, + "profile_background_image_url":"http://a0.twimg.com/images/themes/theme1/bg.png", + "screen_name":"__twython__", + "lang":"en", + "profile_background_tile":false, + "favourites_count":0, + "name":"Twython", + "notifications":false, + "url":null, + "created_at":"Thu May 16 01:11:09 +0000 2013", + "contributors_enabled":false, + "time_zone":null, + "protected":false, + "default_profile":false, + "is_translator":false + }, + "geo":null, + "in_reply_to_user_id_str":null, + "possibly_sensitive":false, + "lang":"en", + "created_at":"Wed Jun 26 00:18:21 +0000 2013", + "in_reply_to_status_id_str":null, + "place":null +} diff --git a/tests/tweets/compat.json b/tests/tweets/compat.json new file mode 100644 index 00000000..b01b358e --- /dev/null +++ b/tests/tweets/compat.json @@ -0,0 +1,51 @@ +{ + "contributors":null, + "truncated":true, + "text":"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", + "is_quote_status":false, + "in_reply_to_status_id":null, + "id":777915304261193728, + "favorite_count":13856, + "source":"Twitter Web Client", + "retweeted":false, + "coordinates":null, + "entities":{ + "symbols":[ + + ], + "user_mentions":[ + + ], + "hashtags":[ + + ], + "urls":[ + { + "url":"https://t.co/SRmsuks2ru", + "indices":[ + 117, + 140 + ], + "expanded_url":"https://twitter.com/i/web/status/777915304261193728", + "display_url":"twitter.com/i/web/status/7\u2026" + } + ] + }, + "in_reply_to_screen_name":null, + "id_str":"777915304261193728", + "retweet_count":14767, + "in_reply_to_user_id":null, + "favorited":false, + "user":{ + "name":"Twitter", + "screen_name":"twitter" + }, + "geo":null, + "in_reply_to_user_id_str":null, + "possibly_sensitive":false, + "possibly_sensitive_appealable":false, + "lang":"en", + "created_at":"Mon Sep 19 17:00:36 +0000 2016", + "in_reply_to_status_id_str":null, + "place":null +} diff --git a/tests/tweets/extended.json b/tests/tweets/extended.json new file mode 100644 index 00000000..105700af --- /dev/null +++ b/tests/tweets/extended.json @@ -0,0 +1,204 @@ +{ + "full_text":"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", + "truncated":false, + "is_quote_status":false, + "in_reply_to_status_id":null, + "id":777915304261193728, + "favorite_count":13856, + "contributors":null, + "source":"Twitter Web Client", + "retweeted":false, + "coordinates":null, + "entities":{ + "symbols":[ + + ], + "user_mentions":[ + + ], + "hashtags":[ + + ], + "urls":[ + + ], + "media":[ + { + "expanded_url":"https://twitter.com/twitter/status/777915304261193728/photo/1", + "sizes":{ + "small":{ + "h":340, + "w":340, + "resize":"fit" + }, + "large":{ + "h":700, + "w":700, + "resize":"fit" + }, + "medium":{ + "h":600, + "w":600, + "resize":"fit" + }, + "thumb":{ + "h":150, + "w":150, + "resize":"crop" + } + }, + "url":"https://t.co/I9pUC0NdZC", + "media_url_https":"https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg", + "id_str":"777914712382058496", + "indices":[ + 140, + 163 + ], + "media_url":"http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg", + "type":"photo", + "id":777914712382058496, + "display_url":"pic.twitter.com/I9pUC0NdZC" + } + ] + }, + "in_reply_to_screen_name":null, + "id_str":"777915304261193728", + "display_text_range":[ + 0, + 139 + ], + "retweet_count":14767, + "in_reply_to_user_id":null, + "favorited":false, + "user":{ + "follow_request_sent":false, + "has_extended_profile":false, + "profile_use_background_image":true, + "id":783214, + "verified":true, + "profile_text_color":"333333", + "profile_image_url_https":"https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg", + "profile_sidebar_fill_color":"F6F6F6", + "is_translator":false, + "geo_enabled":true, + "entities":{ + "url":{ + "urls":[ + { + "url":"http://t.co/5iRhy7wTgu", + "indices":[ + 0, + 22 + ], + "expanded_url":"http://blog.twitter.com/", + "display_url":"blog.twitter.com" + } + ] + }, + "description":{ + "urls":[ + { + "url":"https://t.co/qq1HEzvnrA", + "indices":[ + 84, + 107 + ], + "expanded_url":"http://support.twitter.com", + "display_url":"support.twitter.com" + } + ] + } + }, + "followers_count":56827498, + "protected":false, + "location":"San Francisco, CA", + "default_profile_image":false, + "id_str":"783214", + "lang":"en", + "utc_offset":-25200, + "statuses_count":3161, + "description":"Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.", + "friends_count":145, + "profile_link_color":"226699", + "profile_image_url":"http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg", + "notifications":false, + "profile_background_image_url_https":"https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png", + "profile_background_color":"ACDED6", + "profile_banner_url":"https://pbs.twimg.com/profile_banners/783214/1471929200", + "profile_background_image_url":"http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png", + "name":"Twitter", + "is_translation_enabled":false, + "profile_background_tile":true, + "favourites_count":2332, + "screen_name":"twitter", + "url":"http://t.co/5iRhy7wTgu", + "created_at":"Tue Feb 20 14:35:54 +0000 2007", + "contributors_enabled":false, + "time_zone":"Pacific Time (US & Canada)", + "profile_sidebar_border_color":"FFFFFF", + "default_profile":false, + "following":false, + "listed_count":90445 + }, + "geo":null, + "in_reply_to_user_id_str":null, + "possibly_sensitive":false, + "possibly_sensitive_appealable":false, + "lang":"en", + "created_at":"Mon Sep 19 17:00:36 +0000 2016", + "in_reply_to_status_id_str":null, + "place":null, + "extended_entities":{ + "media":[ + { + "expanded_url":"https://twitter.com/twitter/status/777915304261193728/photo/1", + "display_url":"pic.twitter.com/I9pUC0NdZC", + "url":"https://t.co/I9pUC0NdZC", + "media_url_https":"https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg", + "video_info":{ + "aspect_ratio":[ + 1, + 1 + ], + "variants":[ + { + "url":"https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4", + "bitrate":0, + "content_type":"video/mp4" + } + ] + }, + "id_str":"777914712382058496", + "sizes":{ + "small":{ + "h":340, + "w":340, + "resize":"fit" + }, + "large":{ + "h":700, + "w":700, + "resize":"fit" + }, + "medium":{ + "h":600, + "w":600, + "resize":"fit" + }, + "thumb":{ + "h":150, + "w":150, + "resize":"crop" + } + }, + "indices":[ + 140, + 163 + ], + "type":"animated_gif", + "id":777914712382058496, + "media_url":"http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg" + } + ] + } +} diff --git a/tests/tweets/identical_urls.json b/tests/tweets/identical_urls.json new file mode 100644 index 00000000..69840f9f --- /dev/null +++ b/tests/tweets/identical_urls.json @@ -0,0 +1,34 @@ +{ + "entities":{ + "hashtags":[ + + ], + "user_mentions":[ + + ], + "symbols":[ + + ], + "urls":[ + { + "display_url":"buff.ly/2sEhrgO", + "expanded_url":"http://buff.ly/2sEhrgO", + "indices":[ + 42, + 65 + ], + "url":"https://t.co/W0uArTMk9N" + }, + { + "display_url":"buff.ly/2sEhrgO", + "expanded_url":"http://buff.ly/2sEhrgO", + "indices":[ + 101, + 124 + ], + "url":"https://t.co/W0uArTMk9N" + } + ] + }, + "full_text":"Use Cases, Trials and Making 5G a Reality https://t.co/W0uArTMk9N #5G #innovation via @5GWorldSeries https://t.co/W0uArTMk9N" +} diff --git a/tests/tweets/media.json b/tests/tweets/media.json new file mode 100644 index 00000000..4366794d --- /dev/null +++ b/tests/tweets/media.json @@ -0,0 +1,205 @@ +{ + "contributors":null, + "truncated":false, + "is_quote_status":false, + "in_reply_to_status_id":null, + "id":905105588279013377, + "favorite_count":10, + "full_text":"I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg", + "source":"Twitter Web Client", + "retweeted":false, + "coordinates":null, + "entities":{ + "symbols":[ + + ], + "user_mentions":[ + + ], + "hashtags":[ + + ], + "urls":[ + { + "url":"https://t.co/2yUmmn3TOc", + "indices":[ + 107, + 130 + ], + "expanded_url":"http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php", + "display_url":"gyford.com/phil/writing/2\u2026" + } + ], + "media":[ + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "display_url":"pic.twitter.com/OwNc6uJklg", + "url":"https://t.co/OwNc6uJklg", + "media_url_https":"https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", + "id_str":"905105571765944320", + "sizes":{ + "small":{ + "h":256, + "resize":"fit", + "w":680 + }, + "large":{ + "h":376, + "resize":"fit", + "w":1000 + }, + "medium":{ + "h":376, + "resize":"fit", + "w":1000 + }, + "thumb":{ + "h":150, + "resize":"crop", + "w":150 + } + }, + "indices":[ + 131, + 154 + ], + "type":"photo", + "id":905105571765944320, + "media_url":"http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg" + } + ] + }, + "in_reply_to_screen_name":null, + "in_reply_to_user_id":null, + "display_text_range":[ + 0, + 130 + ], + "retweet_count":1, + "id_str":"905105588279013377", + "favorited":false, + "user":{ + "screen_name":"philgyford", + "name":"Phil Gyford" + }, + "geo":null, + "in_reply_to_user_id_str":null, + "possibly_sensitive":false, + "possibly_sensitive_appealable":false, + "lang":"en", + "created_at":"Tue Sep 05 16:29:22 +0000 2017", + "in_reply_to_status_id_str":null, + "place":null, + "extended_entities":{ + "media":[ + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "display_url":"pic.twitter.com/OwNc6uJklg", + "url":"https://t.co/OwNc6uJklg", + "media_url_https":"https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", + "id_str":"905105571765944320", + "sizes":{ + "small":{ + "h":256, + "resize":"fit", + "w":680 + }, + "large":{ + "h":376, + "resize":"fit", + "w":1000 + }, + "medium":{ + "h":376, + "resize":"fit", + "w":1000 + }, + "thumb":{ + "h":150, + "resize":"crop", + "w":150 + } + }, + "indices":[ + 131, + 154 + ], + "type":"photo", + "id":905105571765944320, + "media_url":"http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg" + }, + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "display_url":"pic.twitter.com/OwNc6uJklg", + "url":"https://t.co/OwNc6uJklg", + "media_url_https":"https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg", + "id_str":"905105572529393668", + "sizes":{ + "large":{ + "h":399, + "resize":"fit", + "w":1000 + }, + "small":{ + "h":271, + "resize":"fit", + "w":680 + }, + "medium":{ + "h":399, + "resize":"fit", + "w":1000 + }, + "thumb":{ + "h":150, + "resize":"crop", + "w":150 + } + }, + "indices":[ + 131, + 154 + ], + "type":"photo", + "id":905105572529393668, + "media_url":"http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg" + }, + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "display_url":"pic.twitter.com/OwNc6uJklg", + "url":"https://t.co/OwNc6uJklg", + "media_url_https":"https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg", + "id_str":"905105573255016448", + "sizes":{ + "small":{ + "h":195, + "resize":"fit", + "w":680 + }, + "large":{ + "h":287, + "resize":"fit", + "w":1000 + }, + "medium":{ + "h":287, + "resize":"fit", + "w":1000 + }, + "thumb":{ + "h":150, + "resize":"crop", + "w":150 + } + }, + "indices":[ + 131, + 154 + ], + "type":"photo", + "id":905105573255016448, + "media_url":"http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg" + } + ] + } +} diff --git a/tests/tweets/quoted.json b/tests/tweets/quoted.json new file mode 100644 index 00000000..c8e372bd --- /dev/null +++ b/tests/tweets/quoted.json @@ -0,0 +1,94 @@ +{ + "contributors":null, + "truncated":false, + "text":"Here\u2019s a quoted tweet. https://t.co/3neKzof0gT", + "is_quote_status":true, + "in_reply_to_status_id":null, + "id":917706313785905157, + "favorite_count":0, + "source":"Twitter Web Client", + "quoted_status_id":917699069916729344, + "retweeted":false, + "coordinates":null, + "quoted_status":{ + "contributors":null, + "truncated":false, + "text":"The quoted tweet text.", + "is_quote_status":false, + "in_reply_to_status_id":null, + "id":917699069916729344, + "favorite_count":1, + "source":"Twitter Web Client", + "retweeted":false, + "coordinates":null, + "entities":{ + "symbols":[ + + ], + "user_mentions":[ + + ], + "hashtags":[ + + ], + "urls":[ + + ] + }, + "in_reply_to_screen_name":null, + "in_reply_to_user_id":null, + "retweet_count":0, + "id_str":"917699069916729344", + "favorited":false, + "user":{ + "screen_name":"philgyford", + "name":"Phil Gyford" + }, + "geo":null, + "in_reply_to_user_id_str":null, + "lang":"ht", + "created_at":"Tue Oct 10 10:31:22 +0000 2017", + "in_reply_to_status_id_str":null, + "place":null + }, + "entities":{ + "symbols":[ + + ], + "user_mentions":[ + + ], + "hashtags":[ + + ], + "urls":[ + { + "url":"https://t.co/3neKzof0gT", + "indices":[ + 23, + 46 + ], + "expanded_url":"https://twitter.com/philgyford/status/917699069916729344", + "display_url":"twitter.com/philgyford/sta\u2026" + } + ] + }, + "in_reply_to_screen_name":null, + "in_reply_to_user_id":null, + "retweet_count":0, + "id_str":"917706313785905157", + "favorited":false, + "user":{ + "screen_name":"philgyfordtest", + "name":"Phil Gyford Test" + }, + "geo":null, + "in_reply_to_user_id_str":null, + "possibly_sensitive":false, + "possibly_sensitive_appealable":false, + "lang":"en", + "created_at":"Tue Oct 10 11:00:10 +0000 2017", + "quoted_status_id_str":"917699069916729344", + "in_reply_to_status_id_str":null, + "place":null +} diff --git a/tests/tweets/reply.json b/tests/tweets/reply.json new file mode 100644 index 00000000..2a227125 --- /dev/null +++ b/tests/tweets/reply.json @@ -0,0 +1,62 @@ +{ + "display_text_range":[ + 12, + 114 + ], + "in_reply_to_status_id_str":"742374355531923456", + "source":"Twitter Web Client", + "geo":null, + "full_text":"@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr", + "extended_entities":{ + "media":[ + + ] + }, + "id_str":"300", + "in_reply_to_status_id":742374355531923456, + "id":300, + "in_reply_to_screen_name":"philgyford", + "retweet_count":0, + "user":{ + + }, + "created_at":"Mon Jun 13 15:48:06 +0000 2016", + "lang":"en", + "favorite_count":0, + "coordinates":null, + "place":null, + "contributors":null, + "in_reply_to_user_id":12552, + "in_reply_to_user_id_str":"12552", + "retweeted":false, + "favorited":false, + "truncated":false, + "entities":{ + "user_mentions":[ + { + "id_str":"12552", + "id":12552, + "screen_name":"philgyford", + "name":"Phil Gyford", + "indices":[ + 0, + 11 + ] + } + ], + "media":[ + + ], + "hashtags":[ + + ], + "symbols":[ + + ], + "urls":[ + + ] + }, + "is_quote_status":false, + "possibly_sensitive":false +} diff --git a/tests/tweets/retweet.json b/tests/tweets/retweet.json new file mode 100644 index 00000000..046bb648 --- /dev/null +++ b/tests/tweets/retweet.json @@ -0,0 +1,91 @@ +{ + "coordinates":null, + "source":"web", + "in_reply_to_user_id_str":null, + "truncated":false, + "in_reply_to_user_id":null, + "favorite_count":0, + "user":{ + "name":"Phil Gyford Test", + "screen_name":"philgyfordtest" + }, + "favorited":false, + "retweeted_status":{ + "coordinates":null, + "source":"Twitter Web Client", + "in_reply_to_user_id_str":null, + "truncated":false, + "in_reply_to_user_id":null, + "favorite_count":21, + "user":{ + "name":"Samuel Pepys", + "screen_name":"samuelpepys" + }, + "favorited":false, + "id":917459832885653506, + "contributors":null, + "in_reply_to_screen_name":null, + "geo":null, + "in_reply_to_status_id_str":null, + "id_str":"917459832885653506", + "entities":{ + "hashtags":[ + + ], + "symbols":[ + + ], + "user_mentions":[ + + ], + "urls":[ + + ] + }, + "in_reply_to_status_id":null, + "text":"My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.", + "retweet_count":3, + "place":null, + "lang":"en", + "created_at":"Mon Oct 09 18:40:44 +0000 2017", + "is_quote_status":false, + "retweeted":false + }, + "id":917712989649801216, + "contributors":null, + "in_reply_to_screen_name":null, + "geo":null, + "in_reply_to_status_id_str":null, + "id_str":"917712989649801216", + "entities":{ + "hashtags":[ + + ], + "symbols":[ + + ], + "user_mentions":[ + { + "id_str":"14475268", + "indices":[ + 3, + 15 + ], + "name":"Samuel Pepys", + "id":14475268, + "screen_name":"samuelpepys" + } + ], + "urls":[ + + ] + }, + "in_reply_to_status_id":null, + "text":"RT @samuelpepys: My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.", + "retweet_count":3, + "place":null, + "lang":"en", + "created_at":"Tue Oct 10 11:26:41 +0000 2017", + "is_quote_status":false, + "retweeted":false +} diff --git a/tests/tweets/symbols.json b/tests/tweets/symbols.json new file mode 100644 index 00000000..7519a081 --- /dev/null +++ b/tests/tweets/symbols.json @@ -0,0 +1,61 @@ +{ + "text":"Some symbols: $AAPL and $PEP and $ANOTHER and $A.", + "contributors":null, + "geo":null, + "favorited":true, + "in_reply_to_user_id_str":null, + "user":{ + "screen_name":"philgyfordtest", + "name":"Phil Gyford Test" + }, + "in_reply_to_user_id":null, + "retweeted":false, + "coordinates":null, + "place":null, + "in_reply_to_status_id":null, + "lang":"en", + "in_reply_to_status_id_str":null, + "truncated":false, + "retweet_count":0, + "is_quote_status":false, + "id":662694880657989632, + "id_str":"662694880657989632", + "in_reply_to_screen_name":null, + "favorite_count":1, + "entities":{ + "hashtags":[ + + ], + "user_mentions":[ + + ], + "symbols":[ + { + "indices":[ + 14, + 19 + ], + "text":"AAPL" + }, + { + "indices":[ + 24, + 28 + ], + "text":"PEP" + }, + { + "indices":[ + 46, + 48 + ], + "text":"A" + } + ], + "urls":[ + + ] + }, + "created_at":"Fri Nov 06 18:15:46 +0000 2015", + "source":"Twitter Web Client" +} From d3f5361f4d58b693f013eeb07dd12b698cae5a58 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Wed, 11 Oct 2017 18:40:55 +0100 Subject: [PATCH 128/165] Try to fix loading of JSON files in tests on Travis --- tests/test_html_for_tweet.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index cc49121e..7331fa4f 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import json +import os from twython import Twython, TwythonError @@ -11,7 +12,11 @@ def setUp(self): self.api = Twython('', '', '', '') def load_tweet(self, name): - f = open('tests/tweets/%s.json' % name) + f = open(os.path.join( + os.path.dirname(__file__), + 'tweets', + '%s.json' % name + )) tweet = json.load(f) f.close() return tweet From f3088b02894053485e0f3a6c46fa468032c99f43 Mon Sep 17 00:00:00 2001 From: Clayton A Davis Date: Wed, 11 Oct 2017 13:58:25 -0400 Subject: [PATCH 129/165] Update links to Twitter docs --- README.rst | 10 +++++----- docs/usage/basic_usage.rst | 2 +- twython/streaming/api.py | 6 +++--- twython/streaming/types.py | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index 061d1b7c..edddaccc 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ Features - Twitter lists - Timelines - Direct Messages - - and anything found in `the docs `_ + - and anything found in `the docs `_ - Image Uploading: - Update user status with an image - Change user avatar @@ -67,7 +67,7 @@ Documentation is available at https://twython.readthedocs.io/en/latest/ Starting Out ------------ -First, you'll want to head over to https://dev.twitter.com/apps and register an application! +First, you'll want to head over to https://apps.twitter.com and register an application! After you register, grab your applications ``Consumer Key`` and ``Consumer Secret`` from the application details tab. @@ -165,7 +165,7 @@ Create a Twython instance with your application keys and the users OAuth tokens Authenticated Users Home Timeline ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Documentation: https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline +Documentation: https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline .. code-block:: python @@ -176,7 +176,7 @@ Updating Status This method makes use of dynamic arguments, `read more about them `_ -Documentation: https://dev.twitter.com/docs/api/1/post/statuses/update +Documentation: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update .. code-block:: python @@ -185,7 +185,7 @@ Documentation: https://dev.twitter.com/docs/api/1/post/statuses/update Searching ~~~~~~~~~ - https://dev.twitter.com/docs/api/1.1/get/search/tweets says it takes "q" and "result_type" amongst other arguments + https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets says it takes "q" and "result_type" amongst other arguments .. code-block:: python diff --git a/docs/usage/basic_usage.rst b/docs/usage/basic_usage.rst index d7e32e3f..8fee8078 100644 --- a/docs/usage/basic_usage.rst +++ b/docs/usage/basic_usage.rst @@ -80,7 +80,7 @@ Documentation: https://developer.twitter.com/en/docs/tweets/search/api-reference .. _dynamicargexplaination: -.. important:: To help explain :ref:`dynamic function arguments ` a little more, you can see that the previous call used the keyword argument ``q``, that is because Twitter specifies in their `search documentation `_ that the search call accepts the parameter "q". You can pass mutiple keyword arguments. The search documentation also specifies that the call accepts the parameter "result_type" +.. important:: To help explain :ref:`dynamic function arguments ` a little more, you can see that the previous call used the keyword argument ``q``, that is because Twitter specifies in their `search documentation `_ that the search call accepts the parameter "q". You can pass mutiple keyword arguments. The search documentation also specifies that the call accepts the parameter "result_type" .. code-block:: python diff --git a/twython/streaming/api.py b/twython/streaming/api.py index 47678e47..0195f380 100644 --- a/twython/streaming/api.py +++ b/twython/streaming/api.py @@ -169,9 +169,9 @@ def on_success(self, data): # pragma: no cover Returns True if other handlers for this message should be invoked. Feel free to override this to handle your streaming data how you - want it handled. - See https://dev.twitter.com/docs/streaming-apis/messages for messages - sent along in stream responses. + want it handled. See + https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/streaming-message-types + for messages sent along in stream responses. :param data: data recieved from the stream :type data: dict diff --git a/twython/streaming/types.py b/twython/streaming/types.py index aa6b9adb..8755a8db 100644 --- a/twython/streaming/types.py +++ b/twython/streaming/types.py @@ -59,7 +59,7 @@ def filter(self, **params): :param \*\*params: Parameters to send with your stream request Accepted params found at: - https://dev.twitter.com/docs/api/1.1/post/statuses/filter + https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter """ url = 'https://stream.twitter.com/%s/statuses/filter.json' \ % self.streamer.api_version @@ -71,7 +71,7 @@ def sample(self, **params): :param \*\*params: Parameters to send with your stream request Accepted params found at: - https://dev.twitter.com/docs/api/1.1/get/statuses/sample + https://developer.twitter.com/en/docs/tweets/sample-realtime/api-reference/get-statuses-sample """ url = 'https://stream.twitter.com/%s/statuses/sample.json' \ % self.streamer.api_version @@ -95,7 +95,7 @@ def set_dynamic_filter(self, **params): :param \*\*params: Parameters to send with your stream request Accepted params found at: - https://dev.twitter.com/docs/api/1.1/post/statuses/filter + https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter """ self.params = params @@ -104,4 +104,4 @@ def dynamic_filter(self): url = 'https://stream.twitter.com/%s/statuses/filter.json' \ % self.streamer.api_version - self.streamer._request(url, 'POST', params=self.params) + self.streamer._request(url, 'POST', params=self.params) From 8f3db4bc8521e8cd0e567b54ac861af5c88a7703 Mon Sep 17 00:00:00 2001 From: Clayton A Davis Date: Wed, 11 Oct 2017 17:51:40 -0400 Subject: [PATCH 130/165] Update endpoint docstrings to match new Twitter docs --- twython/endpoints.py | 296 ++++++++++++++++++++++++++++--------------- 1 file changed, 193 insertions(+), 103 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 1c1d80c6..f3e45284 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -10,8 +10,8 @@ e.g. Twython.retweet(id=12345) -This map is organized the order functions are documented at: -https://dev.twitter.com/docs/api/1.1 +Official documentation for Twitter API endpoints can be found at: +https://developer.twitter.com/en/docs/api-reference-index """ import json @@ -34,7 +34,7 @@ def get_mentions_timeline(self, **params): @screen_name) for the authenticating user. Docs: - https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline + https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-mentions_timeline """ return self.get('statuses/mentions_timeline', params=params) @@ -44,7 +44,8 @@ def get_user_timeline(self, **params): """Returns a collection of the most recent Tweets posted by the user indicated by the screen_name or user_id parameters. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/user_timeline + Docs: + https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline """ return self.get('statuses/user_timeline', params=params) @@ -54,7 +55,8 @@ def get_home_timeline(self, **params): """Returns a collection of the most recent Tweets and retweets posted by the authenticating user and the users they follow. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline + Docs: + https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline """ return self.get('statuses/home_timeline', params=params) @@ -64,7 +66,8 @@ def retweeted_of_me(self, **params): """Returns the most recent tweets authored by the authenticating user that have been retweeted by others. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweets_of_me + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-retweets_of_me """ return self.get('statuses/retweets_of_me', params=params) @@ -74,7 +77,8 @@ def retweeted_of_me(self, **params): def get_retweets(self, **params): """Returns up to 100 of the first retweets of a given tweet. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweets/%3Aid + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-retweet-id """ return self.get('statuses/retweets/%s' % params.get('id'), @@ -83,7 +87,8 @@ def get_retweets(self, **params): def show_status(self, **params): """Returns a single Tweet, specified by the id parameter - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/show/%3Aid + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-show-id """ return self.get('statuses/show/%s' % params.get('id'), params=params) @@ -93,7 +98,8 @@ def lookup_status(self, **params): request, as specified by comma-separated values passed to the id parameter. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/lookup + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-lookup """ return self.post('statuses/lookup', params=params) @@ -101,7 +107,8 @@ def lookup_status(self, **params): def destroy_status(self, **params): """Destroys the status specified by the required ID parameter - Docs: https://dev.twitter.com/docs/api/1.1/post/statuses/destroy/%3Aid + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-destroy-id """ return self.post('statuses/destroy/%s' % params.get('id')) @@ -109,7 +116,8 @@ def destroy_status(self, **params): def update_status(self, **params): """Updates the authenticating user's current status, also known as tweeting - Docs: https://dev.twitter.com/docs/api/1.1/post/statuses/update + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update """ return self.post('statuses/update', params=params) @@ -117,7 +125,8 @@ def update_status(self, **params): def retweet(self, **params): """Retweets a tweet specified by the id parameter - Docs: https://dev.twitter.com/docs/api/1.1/post/statuses/retweet/%3Aid + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-retweet-id """ return self.post('statuses/retweet/%s' % params.get('id')) @@ -127,7 +136,7 @@ def update_status_with_media(self, **params): # pragma: no cover for upload. In other words, it creates a Tweet with a picture attached. Docs: - https://dev.twitter.com/docs/api/1.1/post/statuses/update_with_media + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update_with_media """ warnings.warn( @@ -143,9 +152,10 @@ def upload_media(self, **params): to the 'update_status' method using the 'media_ids' param. Docs: - https://dev.twitter.com/rest/reference/post/media/upload + https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload + """ - # https://dev.twitter.com/rest/reference/get/media/upload-status + # https://developer.twitter.com/en/docs/media/upload-media/api-reference/get-media-upload-status if params and params.get('command', '') == 'STATUS': return self.get('https://upload.twitter.com/1.1/media/upload.json', params=params) @@ -153,7 +163,10 @@ def upload_media(self, **params): def create_metadata(self, **params): """ Adds metadata to a media element, such as image descriptions for visually impaired. - Docs: https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create + + Docs: + https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create + """ params = json.dumps(params) return self.post("https://upload.twitter.com/1.1/media/metadata/create.json", params=params) @@ -171,7 +184,8 @@ def upload_video(self, media, media_type, media_category=None, size=None, check_ Twitter media upload api expects each chunk to be not more than 5mb. We are sending chunk of 1mb each. Docs: - https://dev.twitter.com/rest/public/uploading-media#chunkedupload + https://developer.twitter.com/en/docs/media/upload-media/uploading-media/chunked-media-upload + """ upload_url = 'https://upload.twitter.com/1.1/media/upload.json' if not size: @@ -216,7 +230,7 @@ def upload_video(self, media, media_type, media_category=None, size=None, check_ response = self.post(upload_url, params=params) - # Only get the status if explicity asked to + # Only get the status if explicity asked to # Default to False if check_progress: @@ -250,7 +264,8 @@ def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded representation of a Tweet on third party sites. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/oembed + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-oembed """ return self.get('statuses/oembed', params=params) @@ -259,7 +274,8 @@ def get_retweeters_ids(self, **params): """Returns a collection of up to 100 user IDs belonging to users who have retweeted the tweet specified by the id parameter. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweeters/ids + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-retweeters-ids """ return self.get('statuses/retweeters/ids', params=params) @@ -270,7 +286,8 @@ def get_retweeters_ids(self, **params): def search(self, **params): """Returns a collection of relevant Tweets matching a specified query. - Docs: https://dev.twitter.com/docs/api/1.1/get/search/tweets + Docs: + https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets """ return self.get('search/tweets', params=params) @@ -282,7 +299,8 @@ def search(self, **params): def get_direct_messages(self, **params): """Returns the 20 most recent direct messages sent to the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/get/direct_messages + Docs: + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-messages """ return self.get('direct_messages', params=params) @@ -291,7 +309,8 @@ def get_direct_messages(self, **params): def get_sent_messages(self, **params): """Returns the 20 most recent direct messages sent by the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/get/direct_messages/sent + Docs: + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-sent-message """ return self.get('direct_messages/sent', params=params) @@ -300,7 +319,8 @@ def get_sent_messages(self, **params): def get_direct_message(self, **params): """Returns a single direct message, specified by an id parameter. - Docs: https://dev.twitter.com/docs/api/1.1/get/direct_messages/show + Docs: + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-message """ return self.get('direct_messages/show', params=params) @@ -308,7 +328,8 @@ def get_direct_message(self, **params): def destroy_direct_message(self, **params): """Destroys the direct message specified in the required id parameter - Docs: https://dev.twitter.com/docs/api/1.1/post/direct_messages/destroy + Docs: + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/delete-message """ return self.post('direct_messages/destroy', params=params) @@ -317,7 +338,8 @@ def send_direct_message(self, **params): """Sends a new direct message to the specified user from the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/direct_messages/new + Docs: + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/new-message """ return self.post('direct_messages/new', params=params) @@ -328,7 +350,7 @@ def get_user_ids_of_blocked_retweets(self, **params): user does not want to receive retweets from. Docs: - https://dev.twitter.com/docs/api/1.1/get/friendships/no_retweets/ids + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-no_retweets-ids """ return self.get('friendships/no_retweets/ids', params=params) @@ -337,7 +359,8 @@ def get_friends_ids(self, **params): """Returns a cursored collection of user IDs for every user the specified user is following (otherwise known as their "friends"). - Docs: https://dev.twitter.com/docs/api/1.1/get/friends/ids + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids """ return self.get('friends/ids', params=params) @@ -348,7 +371,8 @@ def get_followers_ids(self, **params): """Returns a cursored collection of user IDs for every user following the specified user. - Docs: https://dev.twitter.com/docs/api/1.1/get/followers/ids + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids """ return self.get('followers/ids', params=params) @@ -359,7 +383,8 @@ def lookup_friendships(self, **params): """Returns the relationships of the authenticating user to the comma-separated list of up to 100 screen_names or user_ids provided. - Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/lookup + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-lookup """ return self.get('friendships/lookup', params=params) @@ -368,7 +393,8 @@ def get_incoming_friendship_ids(self, **params): """Returns a collection of numeric IDs for every user who has a pending request to follow the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/incoming + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-incoming """ return self.get('friendships/incoming', params=params) @@ -379,7 +405,8 @@ def get_outgoing_friendship_ids(self, **params): """Returns a collection of numeric IDs for every protected user for whom the authenticating user has a pending follow request. - Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/outgoing + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-outgoing """ return self.get('friendships/outgoing', params=params) @@ -390,7 +417,8 @@ def create_friendship(self, **params): """Allows the authenticating users to follow the user specified in the ID parameter. - Docs: https://dev.twitter.com/docs/api/1.1/post/friendships/create + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-create """ return self.post('friendships/create', params=params) @@ -399,7 +427,8 @@ def destroy_friendship(self, **params): """Allows the authenticating user to unfollow the user specified in the ID parameter. - Docs: https://dev.twitter.com/docs/api/1.1/post/friendships/destroy + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-destroy """ return self.post('friendships/destroy', params=params) @@ -408,7 +437,8 @@ def update_friendship(self, **params): """Allows one to enable or disable retweets and device notifications from the specified user. - Docs: https://dev.twitter.com/docs/api/1.1/post/friendships/update + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-update """ return self.post('friendships/update', params=params) @@ -417,7 +447,8 @@ def show_friendship(self, **params): """Returns detailed information about the relationship between two arbitrary users. - Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/show + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-show """ return self.get('friendships/show', params=params) @@ -426,7 +457,8 @@ def get_friends_list(self, **params): """Returns a cursored collection of user objects for every user the specified user is following (otherwise known as their "friends"). - Docs: https://dev.twitter.com/docs/api/1.1/get/friends/list + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list """ return self.get('friends/list', params=params) @@ -437,7 +469,8 @@ def get_followers_list(self, **params): """Returns a cursored collection of user objects for users following the specified user. - Docs: https://dev.twitter.com/docs/api/1.1/get/followers/list + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list """ return self.get('followers/list', params=params) @@ -449,7 +482,8 @@ def get_account_settings(self, **params): """Returns settings (including current trend, geo and sleep time information) for the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/get/account/settings + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-account-settings """ return self.get('account/settings', params=params) @@ -460,7 +494,7 @@ def verify_credentials(self, **params): code and an error message if not. Docs: - https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials """ return self.get('account/verify_credentials', params=params) @@ -468,7 +502,8 @@ def verify_credentials(self, **params): def update_account_settings(self, **params): """Updates the authenticating user's settings. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/settings + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-settings """ return self.post('account/settings', params=params) @@ -486,7 +521,8 @@ def update_profile(self, **params): """Sets values that users are able to set under the "Account" tab of their settings page. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile """ return self.post('account/update_profile', params=params) @@ -495,26 +531,35 @@ def update_profile_banner_image(self, **params): # pragma: no cover """Updates the authenticating user's profile background image. Docs: - https://dev.twitter.com/docs/api/1.1/post/account/update_profile_background_image + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile_background_image """ return self.post('account/update_profile_banner', params=params) - def update_profile_colors(self, **params): + def update_profile_colors(self, **params): # pragma: no cover """Sets one or more hex values that control the color scheme of the authenticating user's profile page on twitter.com. + This method is deprecated, replaced by the profile_link_color + parameter to update_profile. + Docs: - https://dev.twitter.com/docs/api/1.1/post/account/update_profile_colors + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile """ + warnings.warn( + 'This method is deprecated. You should use the' + ' profile_link_color parameter in Twython.update_profile instead.', + TwythonDeprecationWarning, + stacklevel=2 + ) return self.post('account/update_profile_colors', params=params) def update_profile_image(self, **params): # pragma: no cover """Updates the authenticating user's profile image. Docs: - https://dev.twitter.com/docs/api/1.1/post/account/update_profile_image + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile_image """ return self.post('account/update_profile_image', params=params) @@ -523,7 +568,8 @@ def list_blocks(self, **params): """Returns a collection of user objects that the authenticating user is blocking. - Docs: https://dev.twitter.com/docs/api/1.1/get/blocks/list + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-blocks-list """ return self.get('blocks/list', params=params) @@ -533,7 +579,8 @@ def list_blocks(self, **params): def list_block_ids(self, **params): """Returns an array of numeric user ids the authenticating user is blocking. - Docs: https://dev.twitter.com/docs/api/1.1/get/blocks/ids + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-blocks-ids """ return self.get('blocks/ids', params=params) @@ -543,7 +590,8 @@ def list_block_ids(self, **params): def create_block(self, **params): """Blocks the specified user from following the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/blocks/create + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-blocks-create """ return self.post('blocks/create', params=params) @@ -552,7 +600,8 @@ def destroy_block(self, **params): """Un-blocks the user specified in the ID parameter for the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/blocks/destroy + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-blocks-destroy """ return self.post('blocks/destroy', params=params) @@ -562,7 +611,8 @@ def lookup_user(self, **params): as specified by comma-separated values passed to the user_id and/or screen_name parameters. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/lookup + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-lookup """ return self.get('users/lookup', params=params) @@ -571,7 +621,8 @@ def show_user(self, **params): """Returns a variety of information about the user specified by the required user_id or screen_name parameter. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/show + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-show """ return self.get('users/show', params=params) @@ -580,7 +631,8 @@ def search_users(self, **params): """Provides a simple, relevance-based search interface to public user accounts on Twitter. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/search + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-search """ return self.get('users/search', params=params) @@ -606,7 +658,7 @@ def remove_profile_banner(self, **params): Returns HTTP 200 upon success. Docs: - https://dev.twitter.com/docs/api/1.1/post/account/remove_profile_banner + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-remove_profile_banner """ return self.post('account/remove_profile_banner', params=params) @@ -615,7 +667,7 @@ def update_profile_background_image(self, **params): """Uploads a profile banner on behalf of the authenticating user. Docs: - https://dev.twitter.com/docs/api/1.1/post/account/update_profile_banner + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile_banner """ return self.post('account/update_profile_background_image', @@ -625,7 +677,8 @@ def get_profile_banner_sizes(self, **params): """Returns a map of the available size variations of the specified user's profile banner. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/profile_banner + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-users-profile_banner """ return self.get('users/profile_banner', params=params) @@ -634,7 +687,8 @@ def list_mutes(self, **params): """Returns a collection of user objects that the authenticating user is muting. - Docs: https://dev.twitter.com/docs/api/1.1/get/mutes/users/list + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-mutes-users-list """ return self.get('mutes/users/list', params=params) @@ -645,7 +699,8 @@ def list_mute_ids(self, **params): """Returns an array of numeric user ids the authenticating user is muting. - Docs: https://dev.twitter.com/docs/api/1.1/get/mutes/users/ids + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-mutes-users-ids """ return self.get('mutes/users/ids', params=params) @@ -656,7 +711,8 @@ def create_mute(self, **params): """Mutes the specified user, preventing their tweets appearing in the authenticating user's timeline. - Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/create + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-mutes-users-create """ return self.post('mutes/users/create', params=params) @@ -665,7 +721,8 @@ def destroy_mute(self, **params): """Un-mutes the user specified in the user or ID parameter for the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/destroy + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-mutes-users-destroy """ return self.post('mutes/users/destroy', params=params) @@ -675,7 +732,7 @@ def get_user_suggestions_by_slug(self, **params): """Access the users in a given category of the Twitter suggested user list. Docs: - https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-suggestions-slug """ return self.get('users/suggestions/%s' % params.get('slug'), @@ -684,7 +741,8 @@ def get_user_suggestions_by_slug(self, **params): def get_user_suggestions(self, **params): """Access to Twitter's suggested user list. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/suggestions + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-suggestions """ return self.get('users/suggestions', params=params) @@ -695,7 +753,7 @@ def get_user_suggestions_statuses_by_slug(self, **params): user. Docs: - https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug/members + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-suggestions-slug-members """ return self.get('users/suggestions/%s/members' % params.get('slug'), @@ -706,7 +764,8 @@ def get_favorites(self, **params): """Returns the 20 most recent Tweets favorited by the authenticating or specified user. - Docs: https://dev.twitter.com/docs/api/1.1/get/favorites/list + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-favorites-list """ return self.get('favorites/list', params=params) @@ -716,7 +775,8 @@ def destroy_favorite(self, **params): """Un-favorites the status specified in the ID parameter as the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/destroy + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-favorites-destroy """ return self.post('favorites/destroy', params=params) @@ -725,7 +785,8 @@ def create_favorite(self, **params): """Favorites the status specified in the ID parameter as the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/create + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-favorites-create """ return self.post('favorites/create', params=params) @@ -735,7 +796,8 @@ def show_lists(self, **params): """Returns all lists the authenticating or specified user subscribes to, including their own. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/list + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-list """ return self.get('lists/list', params=params) @@ -743,7 +805,8 @@ def show_lists(self, **params): def get_list_statuses(self, **params): """Returns a timeline of tweets authored by members of the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/statuses + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-statuses """ return self.get('lists/statuses', params=params) @@ -752,7 +815,8 @@ def get_list_statuses(self, **params): def delete_list_member(self, **params): """Removes the specified member from the list. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-members-destroy """ return self.post('lists/members/destroy', params=params) @@ -760,7 +824,8 @@ def delete_list_member(self, **params): def get_list_memberships(self, **params): """Returns the lists the specified user has been added to. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/memberships + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-memberships """ return self.get('lists/memberships', params=params) @@ -770,7 +835,8 @@ def get_list_memberships(self, **params): def get_list_subscribers(self, **params): """Returns the subscribers of the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/subscribers + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-subscribers """ return self.get('lists/subscribers', params=params) @@ -781,7 +847,7 @@ def subscribe_to_list(self, **params): """Subscribes the authenticated user to the specified list. Docs: - https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/create + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-subscribers-create """ return self.post('lists/subscribers/create', params=params) @@ -789,7 +855,8 @@ def subscribe_to_list(self, **params): def is_list_subscriber(self, **params): """Check if the specified user is a subscriber of the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/subscribers/show + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-subscribers-show """ return self.get('lists/subscribers/show', params=params) @@ -798,7 +865,7 @@ def unsubscribe_from_list(self, **params): """Unsubscribes the authenticated user from the specified list. Docs: - https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/destroy + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-subscribers-destroy """ return self.post('lists/subscribers/destroy', params=params) @@ -808,7 +875,7 @@ def create_list_members(self, **params): list of member ids or screen names. Docs: - https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-members-create_all """ return self.post('lists/members/create_all', params=params) @@ -816,7 +883,8 @@ def create_list_members(self, **params): def is_list_member(self, **params): """Check if the specified user is a member of the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/members/show + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-members-show """ return self.get('lists/members/show', params=params) @@ -824,7 +892,8 @@ def is_list_member(self, **params): def get_list_members(self, **params): """Returns the members of the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/members + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-members """ return self.get('lists/members', params=params) @@ -834,7 +903,8 @@ def get_list_members(self, **params): def add_list_member(self, **params): """Add a member to a list. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/members/create + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-members-create """ return self.post('lists/members/create', params=params) @@ -842,7 +912,8 @@ def add_list_member(self, **params): def delete_list(self, **params): """Deletes the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/destroy + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-destroy """ return self.post('lists/destroy', params=params) @@ -850,7 +921,8 @@ def delete_list(self, **params): def update_list(self, **params): """Updates the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/update + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-update """ return self.post('lists/update', params=params) @@ -858,7 +930,8 @@ def update_list(self, **params): def create_list(self, **params): """Creates a new list for the authenticated user. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/create + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-create """ return self.post('lists/create', params=params) @@ -866,7 +939,8 @@ def create_list(self, **params): def get_specific_list(self, **params): """Returns the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/show + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-show """ return self.get('lists/show', params=params) @@ -874,7 +948,8 @@ def get_specific_list(self, **params): def get_list_subscriptions(self, **params): """Obtain a collection of the lists the specified user is subscribed to. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/subscriptions + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-subscriptions """ return self.get('lists/subscriptions', params=params) @@ -886,7 +961,7 @@ def delete_list_members(self, **params): comma-separated list of member ids or screen names. Docs: - https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-members-destroy_all """ return self.post('lists/members/destroy_all', params=params) @@ -894,7 +969,8 @@ def delete_list_members(self, **params): def show_owned_lists(self, **params): """Returns the lists owned by the specified Twitter user. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/ownerships + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-ownerships """ return self.get('lists/ownerships', params=params) @@ -905,7 +981,8 @@ def show_owned_lists(self, **params): def get_saved_searches(self, **params): """Returns the authenticated user's saved search queries. - Docs: https://dev.twitter.com/docs/api/1.1/get/saved_searches/list + Docs: + https://developer.twitter.com/en/docs/tweets/search/api-reference/get-saved_searches-list """ return self.get('saved_searches/list', params=params) @@ -914,7 +991,7 @@ def show_saved_search(self, **params): """Retrieve the information for the saved search represented by the given id. Docs: - https://dev.twitter.com/docs/api/1.1/get/saved_searches/show/%3Aid + https://developer.twitter.com/en/docs/tweets/search/api-reference/get-saved_searches-show-id """ return self.get('saved_searches/show/%s' % params.get('id'), @@ -923,7 +1000,8 @@ def show_saved_search(self, **params): def create_saved_search(self, **params): """Create a new saved search for the authenticated user. - Docs: https://dev.twitter.com/docs/api/1.1/post/saved_searches/create + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-mutes-users-create """ return self.post('saved_searches/create', params=params) @@ -932,7 +1010,7 @@ def destroy_saved_search(self, **params): """Destroys a saved search for the authenticating user. Docs: - https://dev.twitter.com/docs/api/1.1/post/saved_searches/destroy/%3Aid + https://developer.twitter.com/en/docs/tweets/search/api-reference/post-saved_searches-destroy-id """ return self.post('saved_searches/destroy/%s' % params.get('id'), @@ -942,7 +1020,8 @@ def destroy_saved_search(self, **params): def get_geo_info(self, **params): """Returns all the information about a known place. - Docs: https://dev.twitter.com/docs/api/1.1/get/geo/id/%3Aplace_id + Docs: + https://developer.twitter.com/en/docs/geo/place-information/api-reference/get-geo-id-place_id """ return self.get('geo/id/%s' % params.get('place_id'), params=params) @@ -951,7 +1030,8 @@ def reverse_geocode(self, **params): """Given a latitude and a longitude, searches for up to 20 places that can be used as a place_id when updating a status. - Docs: https://dev.twitter.com/docs/api/1.1/get/geo/reverse_geocode + Docs: + https://developer.twitter.com/en/docs/geo/places-near-location/api-reference/get-geo-reverse_geocode """ return self.get('geo/reverse_geocode', params=params) @@ -959,7 +1039,8 @@ def reverse_geocode(self, **params): def search_geo(self, **params): """Search for places that can be attached to a statuses/update. - Docs: https://dev.twitter.com/docs/api/1.1/get/geo/search + Docs: + https://developer.twitter.com/en/docs/geo/places-near-location/api-reference/get-geo-search """ return self.get('geo/search', params=params) @@ -985,7 +1066,8 @@ def get_place_trends(self, **params): """Returns the top 10 trending topics for a specific WOEID, if trending information is available for it. - Docs: https://dev.twitter.com/docs/api/1.1/get/trends/place + Docs: + https://developer.twitter.com/en/docs/trends/trends-for-location/api-reference/get-trends-place """ return self.get('trends/place', params=params) @@ -993,7 +1075,8 @@ def get_place_trends(self, **params): def get_available_trends(self, **params): """Returns the locations that Twitter has trending topic information for. - Docs: https://dev.twitter.com/docs/api/1.1/get/trends/available + Docs: + https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trends-available """ return self.get('trends/available', params=params) @@ -1002,7 +1085,8 @@ def get_closest_trends(self, **params): """Returns the locations that Twitter has trending topic information for, closest to a specified location. - Docs: https://dev.twitter.com/docs/api/1.1/get/trends/closest + Docs: + https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trends-closest """ return self.get('trends/closest', params=params) @@ -1011,7 +1095,8 @@ def get_closest_trends(self, **params): def report_spam(self, **params): # pragma: no cover """Report the specified user as a spam account to Twitter. - Docs: https://dev.twitter.com/docs/api/1.1/post/users/report_spam + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-users-report_spam """ return self.post('users/report_spam', params=params) @@ -1021,7 +1106,8 @@ def invalidate_token(self, **params): # pragma: no cover """Allows a registered application to revoke an issued OAuth 2 Bearer Token by presenting its client credentials. - Docs: https://dev.twitter.com/docs/api/1.1/post/oauth2/invalidate_token + Docs: + https://developer.twitter.com/en/docs/basics/authentication/api-reference/invalidate_token """ return self.post('oauth2/invalidate_token', params=params) @@ -1030,7 +1116,8 @@ def invalidate_token(self, **params): # pragma: no cover def get_twitter_configuration(self, **params): """Returns the current configuration used by Twitter - Docs: https://dev.twitter.com/docs/api/1.1/get/help/configuration + Docs: + https://developer.twitter.com/en/docs/developer-utilities/configuration/api-reference/get-help-configuration """ return self.get('help/configuration', params=params) @@ -1039,7 +1126,8 @@ def get_supported_languages(self, **params): """Returns the list of languages supported by Twitter along with their ISO 639-1 code. - Docs: https://dev.twitter.com/docs/api/1.1/get/help/languages + Docs: + https://developer.twitter.com/en/docs/developer-utilities/supported-languages/api-reference/get-help-languages """ return self.get('help/languages', params=params) @@ -1047,7 +1135,8 @@ def get_supported_languages(self, **params): def get_privacy_policy(self, **params): """Returns Twitter's Privacy Policy - Docs: https://dev.twitter.com/docs/api/1.1/get/help/privacy + Docs: + https://developer.twitter.com/en/docs/developer-utilities/privacy-policy/api-reference/get-help-privacy """ return self.get('help/privacy', params=params) @@ -1055,7 +1144,8 @@ def get_privacy_policy(self, **params): def get_tos(self, **params): """Return the Twitter Terms of Service - Docs: https://dev.twitter.com/docs/api/1.1/get/help/tos + Docs: + https://developer.twitter.com/en/docs/developer-utilities/terms-of-service/api-reference/get-help-tos """ return self.get('help/tos', params=params) @@ -1065,13 +1155,13 @@ def get_application_rate_limit_status(self, **params): specified resource families. Docs: - https://dev.twitter.com/docs/api/1.1/get/application/rate_limit_status + https://developer.twitter.com/en/docs/developer-utilities/rate-limit-status/api-reference/get-application-rate_limit_status """ return self.get('application/rate_limit_status', params=params) -# from https://dev.twitter.com/docs/error-codes-responses +# from https://developer.twitter.com/en/docs/ads/general/guides/response-codes TWITTER_HTTP_STATUS_CODE = { 200: ('OK', 'Success!'), 304: ('Not Modified', 'There was no new data to return.'), From 0f64978a08fe36281b1a24e27485c821e5cc4d0d Mon Sep 17 00:00:00 2001 From: Clayton A Davis Date: Wed, 18 Oct 2017 14:01:34 -0400 Subject: [PATCH 131/165] Fix typo in cursor example --- docs/usage/special_functions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/special_functions.rst b/docs/usage/special_functions.rst index cde8f992..9d400763 100644 --- a/docs/usage/special_functions.rst +++ b/docs/usage/special_functions.rst @@ -45,7 +45,7 @@ Another example: .. code-block:: python - results = twitter.cursor(t.get_mentions_timeline) + results = twitter.cursor(twitter.get_mentions_timeline) for result in results: print(result['id_str']) From db40a1c56c57e711937171e1072a7f965354440c Mon Sep 17 00:00:00 2001 From: Clayton A Davis Date: Wed, 18 Oct 2017 14:02:56 -0400 Subject: [PATCH 132/165] Add doc for cursor's return_pages kwarg --- docs/usage/special_functions.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/usage/special_functions.rst b/docs/usage/special_functions.rst index 9d400763..301abd35 100644 --- a/docs/usage/special_functions.rst +++ b/docs/usage/special_functions.rst @@ -49,6 +49,19 @@ Another example: for result in results: print(result['id_str']) +Items vs Pages +^^^^^^^^^^^^^^ + +By default, the cursor yields one item at a time. If instead you prefer to work with entire pages of results, specify ``return_pages=True`` as a keyword argument. + +.. code-block:: python + + results = twitter.cursor(twitter.get_mentions_timeline, return_pages=True) + # page is a list + for page in results: + for result in page: + print(result['id_str']) + HTML for Tweet -------------- From 05fbf6b8b261f70cd6c9f5bbcbb923983a7d836a Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Mon, 6 Nov 2017 10:35:26 -0500 Subject: [PATCH 133/165] - Cleaned up some docstrings with Sphinx markup and param names. --- HISTORY.rst | 4 ++++ twython/endpoints.py | 46 +++++++++++++++++++------------------- twython/streaming/types.py | 7 +++--- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 4249539a..129f5e98 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,10 @@ History ------- +Unreleased +++++++++++++++++++ +- Cleaned up some docstrings with Sphinx markup and param names. + 3.6.0 (2017-23-08) ++++++++++++++++++ - Improve replacing of entities with links in `html_for_tweet()` diff --git a/twython/endpoints.py b/twython/endpoints.py index f3e45284..00392b8d 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -42,7 +42,7 @@ def get_mentions_timeline(self, **params): def get_user_timeline(self, **params): """Returns a collection of the most recent Tweets posted by the user - indicated by the screen_name or user_id parameters. + indicated by the ``screen_name`` or ``user_id`` parameters. Docs: https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline @@ -85,7 +85,7 @@ def get_retweets(self, **params): params=params) def show_status(self, **params): - """Returns a single Tweet, specified by the id parameter + """Returns a single Tweet, specified by the ``id`` parameter Docs: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-show-id @@ -95,7 +95,7 @@ def show_status(self, **params): def lookup_status(self, **params): """Returns fully-hydrated tweet objects for up to 100 tweets per - request, as specified by comma-separated values passed to the id + request, as specified by comma-separated values passed to the ``id`` parameter. Docs: @@ -105,7 +105,7 @@ def lookup_status(self, **params): return self.post('statuses/lookup', params=params) def destroy_status(self, **params): - """Destroys the status specified by the required ID parameter + """Destroys the status specified by the required ``id`` parameter Docs: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-destroy-id @@ -123,7 +123,7 @@ def update_status(self, **params): return self.post('statuses/update', params=params) def retweet(self, **params): - """Retweets a tweet specified by the id parameter + """Retweets a tweet specified by the ``id`` parameter Docs: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-retweet-id @@ -149,7 +149,7 @@ def update_status_with_media(self, **params): # pragma: no cover def upload_media(self, **params): """Uploads media file to Twitter servers. The file will be available to be attached to a status for 60 minutes. To attach to a update, pass a list of returned media ids - to the 'update_status' method using the 'media_ids' param. + to the :meth:`update_status` method using the ``media_ids`` param. Docs: https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload @@ -174,7 +174,7 @@ def create_metadata(self, **params): def upload_video(self, media, media_type, media_category=None, size=None, check_progress=False): """Uploads video file to Twitter servers in chunks. The file will be available to be attached to a status for 60 minutes. To attach to a update, pass a list of returned media ids - to the 'update_status' method using the 'media_ids' param. + to the :meth:`update_status` method using the ``media_ids`` param. Upload happens in 3 stages: - INIT call with size of media to be uploaded(in bytes). If this is more than 15mb, twitter will return error. @@ -272,7 +272,7 @@ def get_oembed_tweet(self, **params): def get_retweeters_ids(self, **params): """Returns a collection of up to 100 user IDs belonging to users who - have retweeted the tweet specified by the id parameter. + have retweeted the tweet specified by the ``id`` parameter. Docs: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-retweeters-ids @@ -317,7 +317,7 @@ def get_sent_messages(self, **params): get_sent_messages.iter_mode = 'id' def get_direct_message(self, **params): - """Returns a single direct message, specified by an id parameter. + """Returns a single direct message, specified by an ``id`` parameter. Docs: https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-message @@ -326,7 +326,7 @@ def get_direct_message(self, **params): return self.get('direct_messages/show', params=params) def destroy_direct_message(self, **params): - """Destroys the direct message specified in the required id parameter + """Destroys the direct message specified in the required ``id`` parameter Docs: https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/delete-message @@ -381,7 +381,7 @@ def get_followers_ids(self, **params): def lookup_friendships(self, **params): """Returns the relationships of the authenticating user to the - comma-separated list of up to 100 screen_names or user_ids provided. + comma-separated list of up to 100 ``screen_names`` or ``user_ids`` provided. Docs: https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-lookup @@ -415,7 +415,7 @@ def get_outgoing_friendship_ids(self, **params): def create_friendship(self, **params): """Allows the authenticating users to follow the user specified - in the ID parameter. + in the ``id`` parameter. Docs: https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-create @@ -425,7 +425,7 @@ def create_friendship(self, **params): def destroy_friendship(self, **params): """Allows the authenticating user to unfollow the user specified - in the ID parameter. + in the ``id`` parameter. Docs: https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-destroy @@ -540,8 +540,8 @@ def update_profile_colors(self, **params): # pragma: no cover """Sets one or more hex values that control the color scheme of the authenticating user's profile page on twitter.com. - This method is deprecated, replaced by the profile_link_color - parameter to update_profile. + This method is deprecated, replaced by the ``profile_link_color`` + parameter to :meth:`update_profile`. Docs: https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile @@ -597,7 +597,7 @@ def create_block(self, **params): return self.post('blocks/create', params=params) def destroy_block(self, **params): - """Un-blocks the user specified in the ID parameter for the + """Un-blocks the user specified in the ``id`` parameter for the authenticating user. Docs: @@ -608,8 +608,8 @@ def destroy_block(self, **params): def lookup_user(self, **params): """Returns fully-hydrated user objects for up to 100 users per request, - as specified by comma-separated values passed to the user_id and/or - screen_name parameters. + as specified by comma-separated values passed to the ``user_id`` and/or + ``screen_name`` parameters. Docs: https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-lookup @@ -619,7 +619,7 @@ def lookup_user(self, **params): def show_user(self, **params): """Returns a variety of information about the user specified by the - required user_id or screen_name parameter. + required ``user_id`` or ``screen_name`` parameter. Docs: https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-show @@ -718,7 +718,7 @@ def create_mute(self, **params): return self.post('mutes/users/create', params=params) def destroy_mute(self, **params): - """Un-mutes the user specified in the user or ID parameter for + """Un-mutes the user specified in the user or ``id`` parameter for the authenticating user. Docs: @@ -772,7 +772,7 @@ def get_favorites(self, **params): get_favorites.iter_mode = 'id' def destroy_favorite(self, **params): - """Un-favorites the status specified in the ID parameter as the + """Un-favorites the status specified in the ``id`` parameter as the authenticating user. Docs: @@ -782,7 +782,7 @@ def destroy_favorite(self, **params): return self.post('favorites/destroy', params=params) def create_favorite(self, **params): - """Favorites the status specified in the ID parameter as the + """Favorites the status specified in the ``id`` parameter as the authenticating user. Docs: @@ -988,7 +988,7 @@ def get_saved_searches(self, **params): return self.get('saved_searches/list', params=params) def show_saved_search(self, **params): - """Retrieve the information for the saved search represented by the given id. + """Retrieve the information for the saved search represented by the given ``id``. Docs: https://developer.twitter.com/en/docs/tweets/search/api-reference/get-saved_searches-show-id diff --git a/twython/streaming/types.py b/twython/streaming/types.py index 8755a8db..81c5c07f 100644 --- a/twython/streaming/types.py +++ b/twython/streaming/types.py @@ -44,9 +44,10 @@ def site(self, **params): class TwythonStreamerTypesStatuses(object): """Class for different statuses endpoints - Available so TwythonStreamer.statuses.filter() is available. - Just a bit cleaner than TwythonStreamer.statuses_filter(), - statuses_sample(), etc. all being single methods in TwythonStreamer + Available so :meth:`TwythonStreamer.statuses.filter()` is available. + Just a bit cleaner than :meth:`TwythonStreamer.statuses_filter()`, + :meth:`statuses_sample()`, etc. all being single methods in + :class:`TwythonStreamer`. """ def __init__(self, streamer): From 9b46ca5845685d525ad92a67b9a386311dff9672 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Mon, 6 Nov 2017 12:49:10 -0500 Subject: [PATCH 134/165] Update HISTORY.rst --- HISTORY.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 129f5e98..4087a68e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,9 +3,6 @@ History ------- -Unreleased -++++++++++++++++++ -- Cleaned up some docstrings with Sphinx markup and param names. 3.6.0 (2017-23-08) ++++++++++++++++++ From b009ed30b84cc41d624c6e68ff31ee138dc796d6 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Sat, 6 Jan 2018 17:27:18 +0000 Subject: [PATCH 135/165] Removing unused file Sorry, I'm not sure how or why I added this in, but it's not needed by anything! --- tweet.json | 142 ----------------------------------------------------- 1 file changed, 142 deletions(-) delete mode 100644 tweet.json diff --git a/tweet.json b/tweet.json deleted file mode 100644 index 5e61ed8b..00000000 --- a/tweet.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "source":"web", - "entities":{ - "user_mentions":[ ], - "media":[ - { - "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", - "indices":[ 131, 154 ], - "url":"https://t.co/OwNc6uJklg", - "media_url":"http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", - "id_str":"905105571765944320", - "id":905105571765944320, - "media_url_https":"https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", - "sizes":[ - { - "h":256, - "resize":"fit", - "w":680 - }, - { - "h":376, - "resize":"fit", - "w":1000 - }, - { - "h":150, - "resize":"crop", - "w":150 - }, - { - "h":376, - "resize":"fit", - "w":1000 - }, - { - "h":376, - "resize":"fit", - "w":1000 - } - ], - "display_url":"pic.twitter.com/OwNc6uJklg" - }, - { - "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", - "indices":[ 131, 154 ], - "url":"https://t.co/OwNc6uJklg", - "media_url":"http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg", - "id_str":"905105572529393668", - "id":905105572529393668, - "media_url_https":"https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg", - "sizes":[ - { - "h":399, - "resize":"fit", - "w":1000 - }, - { - "h":271, - "resize":"fit", - "w":680 - }, - { - "h":399, - "resize":"fit", - "w":1000 - }, - { - "h":150, - "resize":"crop", - "w":150 - }, - { - "h":399, - "resize":"fit", - "w":1000 - } - ], - "display_url":"pic.twitter.com/OwNc6uJklg" - }, - { - "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", - "indices":[ 131, 154 ], - "url":"https://t.co/OwNc6uJklg", - "media_url":"http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg", - "id_str":"905105573255016448", - "id":905105573255016448, - "media_url_https":"https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg", - "sizes":[ - { - "h":287, - "resize":"fit", - "w":1000 - }, - { - "h":195, - "resize":"fit", - "w":680 - }, - { - "h":150, - "resize":"crop", - "w":150 - }, - { - "h":287, - "resize":"fit", - "w":1000 - }, - { - "h":287, - "resize":"fit", - "w":1000 - } - ], - "display_url":"pic.twitter.com/OwNc6uJklg" - } - ], - "hashtags":[ ], - "urls":[ - { - "indices":[ 107, 130 ], - "url":"https://t.co/2yUmmn3TOc", - "expanded_url":"http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php", - "display_url":"gyford.com/phil/writing/2\u2026" - } - ] - }, - "geo":{ }, - "id_str":"905105588279013377", - "text":"I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg", - "id":905105588279013377, - "created_at":"2017-09-05 16:29:22 +0000", - "user":{ - "name":"Phil Gyford", - "screen_name":"philgyford", - "protected":false, - "id_str":"12552", - "profile_image_url_https":"https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg", - "id":12552, - "verified":false - } -} From 73982c78f4bd541c82916c77d7aeb4ad757e9007 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Sat, 6 Jan 2018 19:03:17 +0000 Subject: [PATCH 136/165] Fix links in tweets when there's a prefix If a tweet had a prefix (@names that it was replying to) then the length of these is counted in the indices that show the locations of entities within the tweet. But we were applying those indices to the 'display' part of the tweet that doesn't include the prefix. So, if the tweet was: @bob Please meet @bill and the prefix was `@bob `, then the indices for linking `@bill` are something like `17,21`. But we were applying the link around `@bill` to the display text part of the tweet, which is: Please meet @bill And so the indices no longer lined up with `@bill`. Now they do, and the same for URLs and hashtags. --- tests/test_html_for_tweet.py | 13 +++++ tests/tweets/entities_with_prefix.json | 68 ++++++++++++++++++++++++++ twython/api.py | 14 +++--- 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 tests/tweets/entities_with_prefix.json diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index 7331fa4f..934c2dc3 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -89,6 +89,19 @@ def test_extendedmode(self): self.assertEqual(tweet_text, 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC') + def test_entities_with_prefix(self): + """ + If there is a username mention at the start of a tweet it's in the + "prefix" and so isn't part of the main tweet display text. + But its length is still counted in the indices of any subsequent + mentions, urls, hashtags, etc. + """ + self.maxDiff = 2000 + tweet_object = self.load_tweet('entities_with_prefix') + tweet_text = self.api.html_for_tweet(tweet_object) + self.assertEqual(tweet_text, + '@philgyford This is a test for @visionphil that includes a link example.org and #hashtag and 😃 for good measure AND that is longer than 140 characters. example.com') + def test_media(self): tweet_object = self.load_tweet('media') tweet_text = self.api.html_for_tweet(tweet_object) diff --git a/tests/tweets/entities_with_prefix.json b/tests/tweets/entities_with_prefix.json new file mode 100644 index 00000000..72043f95 --- /dev/null +++ b/tests/tweets/entities_with_prefix.json @@ -0,0 +1,68 @@ +{ + "created_at":"Sat Jan 06 18:56:35 +0000 2018", + "id":949716340755091458, + "id_str":"949716340755091458", + "full_text":"@philgyford This is a test for @visionphil that includes a link https://t.co/sKw4J3A8SZ and #hashtag and \ud83d\ude03 for good measure AND that is longer than 140 characters. https://t.co/jnQdy7Zg7u", + "truncated":false, + "display_text_range":[ 12, 187 ], + "entities":{ + "hashtags":[ + { + "text":"hashtag", + "indices":[ 92, 100 ] + } + ], + "symbols":[ ], + "user_mentions":[ + { + "screen_name":"philgyford", + "name":"Phil Gyford", + "id":12552, + "id_str":"12552", + "indices":[ 0, 11 ] + }, + { + "screen_name":"visionphil", + "name":"Vision Phil", + "id":104456050, + "id_str":"104456050", + "indices":[ 31, 42 ] + } + ], + "urls":[ + { + "url":"https://t.co/sKw4J3A8SZ", + "expanded_url":"http://example.org", + "display_url":"example.org", + "indices":[ 64, 87 ] + }, + { + "url":"https://t.co/jnQdy7Zg7u", + "expanded_url":"http://example.com", + "display_url":"example.com", + "indices":[ 164, 187 ] + } + ] + }, + "source":"Tweetbot for Mac", + "in_reply_to_status_id":948561036889722880, + "in_reply_to_status_id_str":"948561036889722880", + "in_reply_to_user_id":12552, + "in_reply_to_user_id_str":"12552", + "in_reply_to_screen_name":"philgyford", + "user":{ + "id":2030131, + "id_str":"2030131" + }, + "geo":null, + "coordinates":null, + "place":null, + "contributors":null, + "is_quote_status":false, + "retweet_count":0, + "favorite_count":0, + "favorited":false, + "retweeted":false, + "possibly_sensitive":false, + "lang":"en" +} diff --git a/twython/api.py b/twython/api.py index f25cc970..57a10a6a 100644 --- a/twython/api.py +++ b/twython/api.py @@ -581,6 +581,8 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q if display_text_start <= temp['start'] <= display_text_end: temp['replacement'] = mention_html + temp['start'] -= display_text_start + temp['end'] -= display_text_start entities.append(temp) else: # Make the '@username' at the start, before @@ -592,8 +594,8 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q if 'hashtags' in tweet['entities']: for entity in tweet['entities']['hashtags']: temp = {} - temp['start'] = entity['indices'][0] - temp['end'] = entity['indices'][1] + temp['start'] = entity['indices'][0] - display_text_start + temp['end'] = entity['indices'][1] - display_text_start url_html = '#%(hashtag)s' % {'hashtag': entity['text']} @@ -604,8 +606,8 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q if 'symbols' in tweet['entities']: for entity in tweet['entities']['symbols']: temp = {} - temp['start'] = entity['indices'][0] - temp['end'] = entity['indices'][1] + temp['start'] = entity['indices'][0] - display_text_start + temp['end'] = entity['indices'][1] - display_text_start url_html = '$%(symbol)s' % {'symbol': entity['text']} @@ -616,8 +618,8 @@ def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_q if 'urls' in tweet['entities']: for entity in tweet['entities']['urls']: temp = {} - temp['start'] = entity['indices'][0] - temp['end'] = entity['indices'][1] + temp['start'] = entity['indices'][0] - display_text_start + temp['end'] = entity['indices'][1] - display_text_start if use_display_url and entity.get('display_url') and not use_expanded_url: shown_url = entity['display_url'] From 2cfdaaf6e44ced237edc493147c16a38a60926eb Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Sat, 6 Jan 2018 19:53:00 +0000 Subject: [PATCH 137/165] Remove an emoji from a tweet to make test work Rendering emojis in tweets in python 2 may be an entirely different issue to tackle... --- tests/test_html_for_tweet.py | 4 ++-- tests/tweets/entities_with_prefix.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index 934c2dc3..921dfeeb 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -96,11 +96,11 @@ def test_entities_with_prefix(self): But its length is still counted in the indices of any subsequent mentions, urls, hashtags, etc. """ - self.maxDiff = 2000 + self.maxDiff = 2200 tweet_object = self.load_tweet('entities_with_prefix') tweet_text = self.api.html_for_tweet(tweet_object) self.assertEqual(tweet_text, - '@philgyford This is a test for @visionphil that includes a link example.org and #hashtag and 😃 for good measure AND that is longer than 140 characters. example.com') + u'@philgyford This is a test for @visionphil that includes a link example.org and #hashtag and X for good measure AND that is longer than 140 characters. example.com') def test_media(self): tweet_object = self.load_tweet('media') diff --git a/tests/tweets/entities_with_prefix.json b/tests/tweets/entities_with_prefix.json index 72043f95..8ba89753 100644 --- a/tests/tweets/entities_with_prefix.json +++ b/tests/tweets/entities_with_prefix.json @@ -2,7 +2,7 @@ "created_at":"Sat Jan 06 18:56:35 +0000 2018", "id":949716340755091458, "id_str":"949716340755091458", - "full_text":"@philgyford This is a test for @visionphil that includes a link https://t.co/sKw4J3A8SZ and #hashtag and \ud83d\ude03 for good measure AND that is longer than 140 characters. https://t.co/jnQdy7Zg7u", + "full_text":"@philgyford This is a test for @visionphil that includes a link https://t.co/sKw4J3A8SZ and #hashtag and X for good measure AND that is longer than 140 characters. https://t.co/jnQdy7Zg7u", "truncated":false, "display_text_range":[ 12, 187 ], "entities":{ From c9e8a462000898dcd91b9babf130b907986591ea Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Mon, 7 May 2018 12:52:54 -0400 Subject: [PATCH 138/165] Version 3.7.0 --- HISTORY.rst | 9 +++++++++ docs/conf.py | 4 ++-- .../get_direct_messages.py | 0 setup.py | 2 +- twython/__init__.py | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) rename get_direct_message example => examples/get_direct_messages.py (100%) diff --git a/HISTORY.rst b/HISTORY.rst index 4087a68e..75964fff 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,15 @@ History ------- +3.7.0 (2018-07-05) +++++++++++++++++++ +- Fixes for cursoring API endpoints +- Improve `html_for_tweet()` parsing +- Documentation cleanup +- Documentation for cursor's `return_pages` keyword argument +- Update links to Twitter API in documentation +- Added `create_metadata` endpoint +- Raise error for when cursor is not provided a callable 3.6.0 (2017-23-08) ++++++++++++++++++ diff --git a/docs/conf.py b/docs/conf.py index 601d0660..8f3c3d25 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '3.6.0' +version = '3.7.0' # The full version, including alpha/beta/rc tags. -release = '3.6.0' +release = '3.7.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/get_direct_message example b/examples/get_direct_messages.py similarity index 100% rename from get_direct_message example rename to examples/get_direct_messages.py diff --git a/setup.py b/setup.py index 40b05b7f..11cc2b43 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.6.0' +__version__ = '3.7.0' packages = [ 'twython', diff --git a/twython/__init__.py b/twython/__init__.py index 84e9ee63..dc161d13 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ """ __author__ = 'Ryan McGrath ' -__version__ = '3.6.0' +__version__ = '3.7.0' from .api import Twython from .streaming import TwythonStreamer From 36fda7ac02a8047d11ad4de12161a6ffd1e869ea Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Sat, 22 Sep 2018 12:01:57 -0500 Subject: [PATCH 139/165] Added new endpoints for direct messages --- twython/api.py | 22 +++++++++++++++------- twython/endpoints.py | 12 ++++++------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/twython/api.py b/twython/api.py index 4953129f..10c4963c 100644 --- a/twython/api.py +++ b/twython/api.py @@ -135,13 +135,13 @@ def __init__(self, app_key=None, app_secret=None, oauth_token=None, def __repr__(self): return '' % (self.app_key) - def _request(self, url, method='GET', params=None, api_call=None): + def _request(self, url, method='GET', params=None, api_call=None, json_encoded=False): """Internal request method""" method = method.lower() params = params or {} func = getattr(self.client, method) - if isinstance(params, dict): + if isinstance(params, dict) and json_encoded == False: params, files = _transparent_params(params) else: params = params @@ -156,8 +156,13 @@ def _request(self, url, method='GET', params=None, api_call=None): if method == 'get': requests_args['params'] = params else: + # Check for json_encoded so we will sent params as "data" or "json" + if json_encoded: + data_key = "json" + else: + data_key = "data" requests_args.update({ - 'data': params, + data_key: params, 'files': files, }) try: @@ -230,7 +235,7 @@ def _get_error_message(self, response): return error_message - def request(self, endpoint, method='GET', params=None, version='1.1'): + def request(self, endpoint, method='GET', params=None, version='1.1', json_encoded=False): """Return dict of response received from Twitter's API :param endpoint: (required) Full url or Twitter API endpoint @@ -246,6 +251,9 @@ def request(self, endpoint, method='GET', params=None, version='1.1'): :param version: (optional) Twitter API version to access (default 1.1) :type version: string + :param json_encoded: (optional) Flag to indicate if this method should send data encoded as json + (default False) + :type json_encoded: bool :rtype: dict """ @@ -261,7 +269,7 @@ def request(self, endpoint, method='GET', params=None, version='1.1'): url = '%s/%s.json' % (self.api_url % version, endpoint) content = self._request(url, method=method, params=params, - api_call=url) + api_call=url, json_encoded=json_encoded) return content @@ -269,9 +277,9 @@ def get(self, endpoint, params=None, version='1.1'): """Shortcut for GET requests via :class:`request`""" return self.request(endpoint, params=params, version=version) - def post(self, endpoint, params=None, version='1.1'): + def post(self, endpoint, params=None, version='1.1', json_encoded=False): """Shortcut for POST requests via :class:`request`""" - return self.request(endpoint, 'POST', params=params, version=version) + return self.request(endpoint, 'POST', params=params, version=version, json_encoded=json_encoded) def get_lastfunction_header(self, header, default_return_value=None): """Returns a specific header from the last API call diff --git a/twython/endpoints.py b/twython/endpoints.py index 00392b8d..f7e0a5a6 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -300,10 +300,10 @@ def get_direct_messages(self, **params): """Returns the 20 most recent direct messages sent to the authenticating user. Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-messages + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/list-events """ - return self.get('direct_messages', params=params) + return self.get('direct_messages/events/list', params=params) get_direct_messages.iter_mode = 'id' def get_sent_messages(self, **params): @@ -320,10 +320,10 @@ def get_direct_message(self, **params): """Returns a single direct message, specified by an ``id`` parameter. Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-message + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-event """ - return self.get('direct_messages/show', params=params) + return self.get('direct_messages/events/show', params=params) def destroy_direct_message(self, **params): """Destroys the direct message specified in the required ``id`` parameter @@ -339,10 +339,10 @@ def send_direct_message(self, **params): authenticating user. Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/new-message + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/new-event """ - return self.post('direct_messages/new', params=params) + return self.post('direct_messages/events/new', params=params, json_encoded=True) # Friends & Followers def get_user_ids_of_blocked_retweets(self, **params): From 340fb4ea162b3e02fb5e245ad8651ec62d34cea5 Mon Sep 17 00:00:00 2001 From: Vishvajit Pathak Date: Sat, 29 Sep 2018 15:18:31 +0530 Subject: [PATCH 140/165] ISSUE-497 : Fixed the api end point in get_oembed_tweet --- twython/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 00392b8d..9da30dbd 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -268,7 +268,7 @@ def get_oembed_tweet(self, **params): https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-oembed """ - return self.get('statuses/oembed', params=params) + return self.get('oembed', params=params) def get_retweeters_ids(self, **params): """Returns a collection of up to 100 user IDs belonging to users who From a8a0777f7266f87ca1f6af921c35f573a71f45e3 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Sat, 29 Sep 2018 11:09:00 -0500 Subject: [PATCH 141/165] Added delete methods to Twython's API --- twython/api.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/twython/api.py b/twython/api.py index 10c4963c..96d1ae72 100644 --- a/twython/api.py +++ b/twython/api.py @@ -153,7 +153,7 @@ def _request(self, url, method='GET', params=None, api_call=None, json_encoded=F if k in ('timeout', 'allow_redirects', 'stream', 'verify'): requests_args[k] = v - if method == 'get': + if method == 'get' or method == 'delete': requests_args['params'] = params else: # Check for json_encoded so we will sent params as "data" or "json" @@ -242,7 +242,7 @@ def request(self, endpoint, method='GET', params=None, version='1.1', json_encod (e.g. search/tweets) :type endpoint: string :param method: (optional) Method of accessing data, either - GET or POST. (default GET) + GET, POST or DELETE. (default GET) :type method: string :param params: (optional) Dict of parameters (if any) accepted the by Twitter API endpoint you are trying to @@ -281,6 +281,10 @@ def post(self, endpoint, params=None, version='1.1', json_encoded=False): """Shortcut for POST requests via :class:`request`""" return self.request(endpoint, 'POST', params=params, version=version, json_encoded=json_encoded) + def delete(self, endpoint, params=None, version='1.1', json_encoded=False): + """Shortcut for delete requests via :class:`request`""" + return self.request(endpoint, 'DELETE', params=params, version=version, json_encoded=json_encoded) + def get_lastfunction_header(self, header, default_return_value=None): """Returns a specific header from the last API call This will return None if the header is not present From 96dd5b289792fff6b5f438471bc6b2ed21f62bcc Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Sat, 29 Sep 2018 11:09:39 -0500 Subject: [PATCH 142/165] Added support for delete direct_messages/events/destroy endpoint --- twython/endpoints.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index f7e0a5a6..9adf800f 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -329,10 +329,10 @@ def destroy_direct_message(self, **params): """Destroys the direct message specified in the required ``id`` parameter Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/delete-message + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/delete-message-event """ - return self.post('direct_messages/destroy', params=params) + return self.delete('direct_messages/events/destroy', params=params) def send_direct_message(self, **params): """Sends a new direct message to the specified user from the From 449807a75965a583f139df16d5e30c1e0e814613 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Sun, 30 Sep 2018 10:53:08 -0500 Subject: [PATCH 143/165] Fixed code styling --- twython/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/twython/api.py b/twython/api.py index 96d1ae72..b0b7287c 100644 --- a/twython/api.py +++ b/twython/api.py @@ -141,7 +141,7 @@ def _request(self, url, method='GET', params=None, api_call=None, json_encoded=F params = params or {} func = getattr(self.client, method) - if isinstance(params, dict) and json_encoded == False: + if isinstance(params, dict) and json_encoded is False: params, files = _transparent_params(params) else: params = params @@ -158,9 +158,9 @@ def _request(self, url, method='GET', params=None, api_call=None, json_encoded=F else: # Check for json_encoded so we will sent params as "data" or "json" if json_encoded: - data_key = "json" + data_key = 'json' else: - data_key = "data" + data_key = 'data' requests_args.update({ data_key: params, 'files': files, From 4f29fd041b960569e46cdf17e08455d40cfc7f66 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 16 Nov 2018 05:34:52 -0600 Subject: [PATCH 144/165] Fixed indentation --- twython/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/api.py b/twython/api.py index b0b7287c..bb10db0a 100644 --- a/twython/api.py +++ b/twython/api.py @@ -160,7 +160,7 @@ def _request(self, url, method='GET', params=None, api_call=None, json_encoded=F if json_encoded: data_key = 'json' else: - data_key = 'data' + data_key = 'data' requests_args.update({ data_key: params, 'files': files, From 02995d7e886fa1b01a1bb0ed0b759d75021835db Mon Sep 17 00:00:00 2001 From: Domenico Nappo Date: Mon, 19 Nov 2018 14:37:31 +0100 Subject: [PATCH 145/165] Adding response.headers to on_error method in twython.streaming.api #503 --- twython/streaming/api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/twython/streaming/api.py b/twython/streaming/api.py index 0195f380..dd4ae897 100644 --- a/twython/streaming/api.py +++ b/twython/streaming/api.py @@ -125,7 +125,7 @@ def _send(retry_counter): self.on_timeout() else: if response.status_code != 200: - self.on_error(response.status_code, response.content) + self.on_error(response.status_code, response.content, response.headers) if self.retry_count and \ (self.retry_count - retry_counter) > 0: @@ -178,7 +178,7 @@ def on_success(self, data): # pragma: no cover """ return True - def on_error(self, status_code, data): # pragma: no cover + def on_error(self, status_code, data, headers=None): # pragma: no cover """Called when stream returns non-200 status code Feel free to override this to handle your streaming data how you @@ -189,6 +189,9 @@ def on_error(self, status_code, data): # pragma: no cover :param data: Error message sent from stream :type data: dict + + :param headers: Response headers sent from the stream (i.e. Retry-After) + :type headers: dict """ return From 554fba43575e61c46e6e4b583484697fccf4a172 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Sun, 10 Mar 2019 20:01:14 +0700 Subject: [PATCH 146/165] MANIFEST.in: Add docs, examples and tests Closes https://github.com/ryanmcgrath/twython/issues/507 --- MANIFEST.in | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index eae175ab..bcfd3149 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,9 @@ include README.rst HISTORY.rst LICENSE requirements.txt + +recursive-include docs * +prune docs/_build + +recursive-include examples *.py + +recursive-include tests *.py +recursive-include tests/tweets *.json From a029433247c4bdd15d56e27f4463a6a8a3ad503f Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Sat, 30 Mar 2019 11:03:51 +0700 Subject: [PATCH 147/165] Add Python 3.7 support Also remove branch restriction in .travis.yml which prevents contributors from seeing builds on their forks. --- .travis.yml | 9 ++++++--- setup.py | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8da6c439..6ec29583 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,12 @@ python: - 2.7 - 3.5 - 3.6 +# Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs +matrix: + include: + - python: 3.7 + dist: xenial + sudo: true env: global: - secure: USjLDneiXlVvEjkUVqTt+LBi0XJ4QhkRcJzqVXA9gEau1NTjAkNTPmHjUbOygp0dkfoV0uWrZKCw6fL1g+HJgWl0vHeHzcNl4mUkA+OwkGFHgaeIhvUfnyyJA8P3Zm21XHC+ehzMpEFN5fVNNhREjnRj+CXMc0FgA6knwBRobu4= @@ -24,9 +30,6 @@ install: script: nosetests -v -w tests/ --logging-filter="twython" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing notifications: email: false -branches: - only: - - master after_success: - coveralls before_script: diff --git a/setup.py b/setup.py index 11cc2b43..3300b4dc 100755 --- a/setup.py +++ b/setup.py @@ -47,5 +47,6 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ] ) From d05fe7516e5a3cd09656ccce6f0a7276dff9e0c6 Mon Sep 17 00:00:00 2001 From: AnnaYasenova Date: Tue, 20 Aug 2019 12:38:20 +0300 Subject: [PATCH 148/165] Update compat.py --- twython/compat.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/twython/compat.py b/twython/compat.py index 7c049b00..2ee7d373 100644 --- a/twython/compat.py +++ b/twython/compat.py @@ -9,6 +9,7 @@ """ import sys +import numpy as np _ver = sys.version_info @@ -29,7 +30,7 @@ str = unicode basestring = basestring - numeric_types = (int, long, float) + numeric_types = (int, long, float, np.int64, np.float64) elif is_py3: @@ -37,4 +38,4 @@ str = str basestring = (str, bytes) - numeric_types = (int, float) + numeric_types = (int, float, np.int64, np.float64) From 1b085180ff6d9cb4e395551682c5a628545cb70c Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Fri, 28 Feb 2020 05:37:05 +1100 Subject: [PATCH 149/165] Fix simple typo: specifcally -> specifically Closes #526 --- twython/advisory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/advisory.py b/twython/advisory.py index 31657ee6..9a0863a8 100644 --- a/twython/advisory.py +++ b/twython/advisory.py @@ -17,6 +17,6 @@ class TwythonDeprecationWarning(DeprecationWarning): """Custom DeprecationWarning to be raised when methods/variables are being deprecated in Twython. Python 2.7 > ignores DeprecationWarning - so we want to specifcally bubble up ONLY Twython Deprecation Warnings + so we want to specifically bubble up ONLY Twython Deprecation Warnings """ pass From 33fccac46b89262d5f41aee7d2e7a82c0891ae26 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 2 Apr 2020 22:48:57 -0700 Subject: [PATCH 150/165] Fix this so Pypi publishing works again --- HISTORY.rst | 86 ++++--------- README.md | 163 +++++++++++++++++++++++ README.rst | 222 -------------------------------- examples/get_direct_messages.py | 14 +- examples/get_user_timeline.py | 2 +- setup.py | 5 +- 6 files changed, 193 insertions(+), 299 deletions(-) create mode 100644 README.md delete mode 100644 README.rst diff --git a/HISTORY.rst b/HISTORY.rst index 75964fff..fd2b8ee3 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,10 +1,10 @@ -.. :changelog: +# History -History -------- +## 3.8.0 (2020-04-02) +- Bump release with latest patches from GitHub. +- Fix Direct Messages with patches from @manuelcortez. -3.7.0 (2018-07-05) -++++++++++++++++++ +## 3.7.0 (2018-07-05) - Fixes for cursoring API endpoints - Improve `html_for_tweet()` parsing - Documentation cleanup @@ -13,32 +13,27 @@ History - Added `create_metadata` endpoint - Raise error for when cursor is not provided a callable -3.6.0 (2017-23-08) -++++++++++++++++++ +## 3.6.0 (2017-23-08) - Improve replacing of entities with links in `html_for_tweet()` - Update classifiers for PyPI -3.5.0 (2017-06-06) -++++++++++++++++++ +## 3.5.0 (2017-06-06) - Added support for "symbols" in `Twython.html_for_tweet()` - Added support for extended tweets in `Twython.html_for_tweet()` - You can now check progress of video uploads to Twitter when using `Twython.upload_video()` -3.4.0 (2016-30-04) -++++++++++++++++++ +## 3.4.0 (2016-30-04) - Added `upload_video` endpoint - Fix quoted status checks in `html_for_tweet` - Fix `html_for_tweet` method response when hashtag/mention is a substring of another -3.3.0 (2015-18-07) -++++++++++++++++++ +## 3.3.0 (2015-18-07) - Added support for muting users - Fix typos in documentation - Updated documentation examples - Added dynamic filtering to streamer -3.2.0 (2014-10-30) -++++++++++++++++++ +## 3.2.0 (2014-10-30) - PEP8'd some code - Added `lookup_status` function to `endpoints.py` - Added keyword argument to `cursor` to return full pages rather than individual results @@ -51,23 +46,16 @@ History - Deprecating `update_with_media` per Twitter API 1.1 (https://dev.twitter.com/rest/reference/post/statuses/update_with_media) - Unpin `requests` and `requests-oauthlib` in `requirements.txt` - -3.1.2 (2013-12-05) -++++++++++++++++++ - +## 3.1.2 (2013-12-05) - Fixed Changelog (HISTORY.rst) -3.1.1 (2013-12-05) -++++++++++++++++++ - +## 3.1.1 (2013-12-05) - Update `requests` version to 2.1.0. - Fixed: Streaming issue where `Exceptions` in handlers or `on_success` which subclass `ValueError` would previously be caught and reported as a JSON decoding problem, and `on_error()` would be called (with status_code=200) - Fixed issue where XML was returned when bad tokens were passed to `get_authorized_tokens` - Fixed import for `setup` causing installation to fail on some devices (eg. Nokia N9/MeeGo) -3.1.0 (2013-09-25) -++++++++++++++++++ - +## 3.1.0 (2013-09-25) - Added ``html_for_tweet`` static method. This method accepts a tweet object returned from a Twitter API call and will return a string with urls, mentions and hashtags in the tweet replaced with HTML. - Pass ``client_args`` to the streaming ``__init__``, much like in core Twython (you can pass headers, timeout, hooks, proxies, etc.). - Streamer has new parameter ``handlers`` which accepts a list of strings related to functions that are apart of the Streaming class and start with "on\_". i.e. ['delete'] is passed, when 'delete' is received from a stream response; ``on_delete`` will be called. @@ -79,9 +67,7 @@ History - Fixed streaming issue where results wouldn't be returned for streams that weren't so active (See https://github.com/ryanmcgrath/twython/issues/202#issuecomment-19915708) - Streaming API now uses ``_transparent_params`` so when passed ``True`` or ``False`` or an array, etc. Twython formats it to meet Twitter parameter standards (i.e. ['ryanmcgrath', 'mikehelmick', 'twitterapi'] would convert to string 'ryanmcgrath,mikehelmick,twitterapi') -3.0.0 (2013-06-18) -++++++++++++++++++ - +## 3.0.0 (2013-06-18) - Changed ``twython/twython.py`` to ``twython/api.py`` in attempt to make structure look a little neater - Removed all camelCase function access (anything like ``getHomeTimeline`` is now ``get_home_timeline``) - Removed ``shorten_url``. With the ``requests`` library, shortening a URL on your own is simple enough @@ -100,9 +86,7 @@ History - Added ``invalidate_token`` API method which allows registed apps to revoke an access token presenting its client credentials - ``get_lastfunction_header`` now accepts a ``default_return_value`` parameter. This means that if you pass a second value (ex. ``Twython.get_lastfunction_header('x-rate-limit-remaining', 0)``) and the value is not found, it returns your default value -2.10.1 (2013-05-29) -++++++++++++++++++ - +## 2.10.1 (2013-05-29) - More test coverage! - Fix ``search_gen`` - Fixed ``get_lastfunction_header`` to actually do what its docstring says, returns ``None`` if header is not found @@ -112,9 +96,7 @@ History - No longer raise ``TwythonStreamError`` when stream line can't be decoded. Instead, sends signal to ``TwythonStreamer.on_error`` - Allow for (int, long, float) params to be passed to Twython Twitter API functions in Python 2, and (int, float) in Python 3 -2.10.0 (2013-05-21) -++++++++++++++++++ - +## 2.10.0 (2013-05-21) - Added ``get_retweeters_ids`` method - Fixed ``TwythonDeprecationWarning`` on camelCase functions if the camelCase was the same as the PEP8 function (i.e. ``Twython.retweet`` did not change) - Fixed error message bubbling when error message returned from Twitter was not an array (i.e. if you try to retweet something twice, the error is not found at index 0) @@ -125,20 +107,14 @@ History - Cleaned up ``Twython.construct_api_url``, uses "transparent" parameters (see 4th bullet in this version for explaination) - Update ``requests`` and ``requests-oauthlib`` requirements, fixing posting files AND post data together, making authenticated requests in general in Python 3.3 -2.9.1 (2013-05-04) -++++++++++++++++++ - +## 2.9.1 (2013-05-04) - "PEP8" all the functions. Switch functions from camelCase() to underscore_funcs(). (i.e. ``updateStatus()`` is now ``update_status()``) -2.9.0 (2013-05-04) -++++++++++++++++++ - +## 2.9.0 (2013-05-04) - Fixed streaming issue #144, added ``TwythonStreamer`` to aid users in a friendly streaming experience (streaming examples in ``examples`` and README's have been updated as well) - ``Twython`` now requires ``requests-oauthlib`` 0.3.1, fixes #154 (unable to upload media when sending POST data with the file) -2.8.0 (2013-04-29) -++++++++++++++++++ - +## 2.8.0 (2013-04-29) - Added a ``HISTORY.rst`` to start tracking history of changes - Updated ``twitter_endpoints.py`` to ``endpoints.py`` for cleanliness - Removed twython3k directory, no longer needed @@ -161,36 +137,24 @@ History - Twython now takes ``ssl_verify`` parameter, defaults True. Set False if you're having development server issues - Removed internal ``_media_update`` function, we could have always just used ``self.post`` -2.7.3 (2013-04-12) -++++++++++++++++++ - +## 2.7.3 (2013-04-12) - Fixed issue where Twython Exceptions were not being logged correctly -2.7.2 (2013-04-08) -++++++++++++++++++ - +## 2.7.2 (2013-04-08) - Fixed ``AttributeError`` when trying to decode the JSON response via ``Response.json()`` -2.7.1 (2013-04-08) -++++++++++++++++++ - +## 2.7.1 (2013-04-08) - Removed ``simplejson`` dependency - Fixed ``destroyDirectMessage``, ``createBlock``, ``destroyBlock`` endpoints in ``twitter_endpoints.py`` - Added ``getProfileBannerSizes`` method to ``twitter_endpoints.py`` - Made oauth_verifier argument required in ``get_authorized_tokens`` - Update ``updateProfileBannerImage`` to use v1.1 endpoint -2.7.0 (2013-04-04) -++++++++++++++++++ - +## 2.7.0 (2013-04-04) - New ``showOwnedLists`` method -2.7.0 (2013-03-31) -++++++++++++++++++ - +## 2.7.0 (2013-03-31) - Added missing slash to ``getMentionsTimeline`` in ``twitter_endpoints.py`` -2.6.0 (2013-03-29) -++++++++++++++++++ - +## 2.6.0 (2013-03-29) - Updated ``twitter_endpoints.py`` to better reflect order of API endpoints on the Twitter API v1.1 docs site diff --git a/README.md b/README.md new file mode 100644 index 00000000..fe0aa1bc --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +# Twython + + + + + + +`Twython` is a Python library providing an easy way to access Twitter data. Supports Python 3. It's been battle tested by companies, educational institutions and individuals alike. Try it today! + +**Note**: As of Twython 3.7.0, there's a general call for maintainers put out. If you find the project useful and want to help out, reach out to Ryan with the info from the bottom of this README. Great open source project to get your feet wet with! + +## Features +- Query data for: + - User information + - Twitter lists + - Timelines + - Direct Messages + - and anything found in [the docs](https://developer.twitter.com/en/docs) +- Image Uploading: + - Update user status with an image + - Change user avatar + - Change user background image + - Change user banner image +- OAuth 2 Application Only (read-only) Support +- Support for Twitter's Streaming API +- Seamless Python 3 support! + +## Installation +Install Twython via pip: + +```bash +$ pip install twython +``` + +Or, if you want the code that is currently on GitHub + +```bash +git clone git://github.com/ryanmcgrath/twython.git +cd twython +python setup.py install +``` + +## Documentation +Documentation is available at https://twython.readthedocs.io/en/latest/ + +## Starting Out +First, you'll want to head over to https://apps.twitter.com and register an application! + +After you register, grab your applications `Consumer Key` and `Consumer Secret` from the application details tab. + +The most common type of authentication is Twitter user authentication using OAuth 1. If you're a web app planning to have users sign up with their Twitter account and interact with their timelines, updating their status, and stuff like that this **is** the authentication for you! + +First, you'll want to import Twython + +```python +from twython import Twython +``` + +## Obtain Authorization URL +Now, you'll want to create a Twython instance with your `Consumer Key` and `Consumer Secret`: + +- Only pass *callback_url* to *get_authentication_tokens* if your application is a Web Application +- Desktop and Mobile Applications **do not** require a callback_url + +```python +APP_KEY = 'YOUR_APP_KEY' +APP_SECRET = 'YOUR_APP_SECRET' + +twitter = Twython(APP_KEY, APP_SECRET) + +auth = twitter.get_authentication_tokens(callback_url='http://mysite.com/callback') +``` + +From the `auth` variable, save the `oauth_token` and `oauth_token_secret` for later use (these are not the final auth tokens). In Django or other web frameworks, you might want to store it to a session variable + +```python +OAUTH_TOKEN = auth['oauth_token'] +OAUTH_TOKEN_SECRET = auth['oauth_token_secret'] +``` + +Send the user to the authentication url, you can obtain it by accessing + +```python +auth['auth_url'] +``` + +## Handling the Callback +If your application is a Desktop or Mobile Application *oauth_verifier* will be the PIN code + +After they authorize your application to access some of their account details, they'll be redirected to the callback url you specified in `get_authentication_tokens`. + +You'll want to extract the `oauth_verifier` from the url. + +Django example: + +```python +oauth_verifier = request.GET['oauth_verifier'] +``` + +Now that you have the `oauth_verifier` stored to a variable, you'll want to create a new instance of Twython and grab the final user tokens + +```python +twitter = Twython( + APP_KEY, APP_SECRET, + OAUTH_TOKEN, OAUTH_TOKEN_SECRET +) + +final_step = twitter.get_authorized_tokens(oauth_verifier) +``` + +Once you have the final user tokens, store them in a database for later use:: + +```python + OAUTH_TOKEN = final_step['oauth_token'] + OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] +``` + +For OAuth 2 (Application Only, read-only) authentication, see [our documentation](https://twython.readthedocs.io/en/latest/usage/starting_out.html#oauth-2-application-authentication). + +## Dynamic Function Arguments +Keyword arguments to functions are mapped to the functions available for each endpoint in the Twitter API docs. Doing this allows us to be incredibly flexible in querying the Twitter API, so changes to the API aren't held up from you using them by this library. + +Basic Usage +----------- + +**Function definitions (i.e. get_home_timeline()) can be found by reading over twython/endpoints.py** + +Create a Twython instance with your application keys and the users OAuth tokens + +```python +from twython import Twython +twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) +``` + +## Authenticated Users Home Timeline +```python +twitter.get_home_timeline() +``` + +## Updating Status +This method makes use of dynamic arguments, [read more about them](https://twython.readthedocs.io/en/latest/usage/starting_out.html#dynamic-function-arguments). + +```python +twitter.update_status(status='See how easy using Twython is!') +``` + +## Advanced Usage +- [Advanced Twython Usage](https://twython.readthedocs.io/en/latest/usage/advanced_usage.html) +- [Streaming with Twython](https://twython.readthedocs.io/en/latest/usage/streaming_api.html) + + +## Questions, Comments, etc? +My hope is that Twython is so simple that you'd never *have* to ask any questions, but if you feel the need to contact me for this (or other) reasons, you can hit me up at ryan@venodesigns.net. + +Or if I'm to busy to answer, feel free to ping mikeh@ydekproductions.com as well. + +Follow us on Twitter: + +- [@ryanmcgrath](https://twitter.com/ryanmcgrath) +- [@mikehelmick](https://twitter.com/mikehelmick) + +## Want to help? +Twython is useful, but ultimately only as useful as the people using it (say that ten times fast!). If you'd like to help, write example code, contribute patches, document things on the wiki, tweet about it. Your help is always appreciated! diff --git a/README.rst b/README.rst deleted file mode 100644 index edddaccc..00000000 --- a/README.rst +++ /dev/null @@ -1,222 +0,0 @@ -Twython -======= - - -.. image:: https://img.shields.io/pypi/v/twython.svg?style=flat-square - :target: https://pypi.python.org/pypi/twython - -.. image:: https://img.shields.io/pypi/dw/twython.svg?style=flat-square - :target: https://pypi.python.org/pypi/twython - -.. image:: https://img.shields.io/travis/ryanmcgrath/twython.svg?style=flat-square - :target: https://travis-ci.org/ryanmcgrath/twython - -.. image:: https://img.shields.io/coveralls/ryanmcgrath/twython/master.svg?style=flat-square - :target: https://coveralls.io/r/ryanmcgrath/twython?branch=master - -``Twython`` is the premier Python library providing an easy (and up-to-date) way to access Twitter data. Actively maintained and featuring support for Python 2.6+ and Python 3. It's been battle tested by companies, educational institutions and individuals alike. Try it today! - -Features --------- - -- Query data for: - - User information - - Twitter lists - - Timelines - - Direct Messages - - and anything found in `the docs `_ -- Image Uploading: - - Update user status with an image - - Change user avatar - - Change user background image - - Change user banner image -- OAuth 2 Application Only (read-only) Support -- Support for Twitter's Streaming API -- Seamless Python 3 support! - -Installation ------------- - -Install Twython via `pip `_ - -.. code-block:: bash - - $ pip install twython - -or, with `easy_install `_ - -.. code-block:: bash - - $ easy_install twython - -But, hey... `that's up to you `_. - -Or, if you want the code that is currently on GitHub - -.. code-block:: bash - - git clone git://github.com/ryanmcgrath/twython.git - cd twython - python setup.py install - -Documentation -------------- - -Documentation is available at https://twython.readthedocs.io/en/latest/ - -Starting Out ------------- - -First, you'll want to head over to https://apps.twitter.com and register an application! - -After you register, grab your applications ``Consumer Key`` and ``Consumer Secret`` from the application details tab. - -The most common type of authentication is Twitter user authentication using OAuth 1. If you're a web app planning to have users sign up with their Twitter account and interact with their timelines, updating their status, and stuff like that this **is** the authentication for you! - -First, you'll want to import Twython - -.. code-block:: python - - from twython import Twython - -Authentication -~~~~~~~~~~~~~~ - -Obtain Authorization URL -^^^^^^^^^^^^^^^^^^^^^^^^ - -Now, you'll want to create a Twython instance with your ``Consumer Key`` and ``Consumer Secret`` - - Only pass *callback_url* to *get_authentication_tokens* if your application is a Web Application - - Desktop and Mobile Applications **do not** require a callback_url - -.. code-block:: python - - APP_KEY = 'YOUR_APP_KEY' - APP_SECRET = 'YOUR_APP_SECRET' - - twitter = Twython(APP_KEY, APP_SECRET) - - auth = twitter.get_authentication_tokens(callback_url='http://mysite.com/callback') - -From the ``auth`` variable, save the ``oauth_token`` and ``oauth_token_secret`` for later use (these are not the final auth tokens). In Django or other web frameworks, you might want to store it to a session variable - -.. code-block:: python - - OAUTH_TOKEN = auth['oauth_token'] - OAUTH_TOKEN_SECRET = auth['oauth_token_secret'] - -Send the user to the authentication url, you can obtain it by accessing - -.. code-block:: python - - auth['auth_url'] - -Handling the Callback -^^^^^^^^^^^^^^^^^^^^^ - - If your application is a Desktop or Mobile Application *oauth_verifier* will be the PIN code - -After they authorize your application to access some of their account details, they'll be redirected to the callback url you specified in ``get_authentication_tokens`` - -You'll want to extract the ``oauth_verifier`` from the url. - -Django example: - -.. code-block:: python - - oauth_verifier = request.GET['oauth_verifier'] - -Now that you have the ``oauth_verifier`` stored to a variable, you'll want to create a new instance of Twython and grab the final user tokens - -.. code-block:: python - - twitter = Twython(APP_KEY, APP_SECRET, - OAUTH_TOKEN, OAUTH_TOKEN_SECRET) - - final_step = twitter.get_authorized_tokens(oauth_verifier) - -Once you have the final user tokens, store them in a database for later use:: - - OAUTH_TOKEN = final_step['oauth_token'] - OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] - -For OAuth 2 (Application Only, read-only) authentication, see `our documentation `_ - -Dynamic Function Arguments -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Keyword arguments to functions are mapped to the functions available for each endpoint in the Twitter API docs. Doing this allows us to be incredibly flexible in querying the Twitter API, so changes to the API aren't held up from you using them by this library. - -Basic Usage ------------ - -**Function definitions (i.e. get_home_timeline()) can be found by reading over twython/endpoints.py** - -Create a Twython instance with your application keys and the users OAuth tokens - -.. code-block:: python - - from twython import Twython - twitter = Twython(APP_KEY, APP_SECRET, - OAUTH_TOKEN, OAUTH_TOKEN_SECRET) - -Authenticated Users Home Timeline -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Documentation: https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline - -.. code-block:: python - - twitter.get_home_timeline() - -Updating Status -~~~~~~~~~~~~~~~ - -This method makes use of dynamic arguments, `read more about them `_ - -Documentation: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update - -.. code-block:: python - - twitter.update_status(status='See how easy using Twython is!') - -Searching -~~~~~~~~~ - - https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets says it takes "q" and "result_type" amongst other arguments - -.. code-block:: python - - twitter.search(q='twitter') - twitter.search(q='twitter', result_type='popular') - -Advanced Usage --------------- - -- `Advanced Twython Usage `_ -- `Streaming with Twython `_ - - -Notes ------ - -- Twython 3.0.0 has been injected with 1000mgs of pure awesomeness! OAuth 2 application authentication is now supported. And a *whole lot* more! See the `CHANGELOG `_ for more details! - -Questions, Comments, etc? -------------------------- - -My hope is that Twython is so simple that you'd never *have* to ask any questions, but if you feel the need to contact me for this (or other) reasons, you can hit me up at ryan@venodesigns.net. - -Or if I'm to busy to answer, feel free to ping mikeh@ydekproductions.com as well. - -Follow us on Twitter: - -- `@ryanmcgrath `_ -- `@mikehelmick `_ - -Want to help? -------------- - -Twython is useful, but ultimately only as useful as the people using it (say that ten times fast!). If you'd like to help, write example code, contribute patches, document things on the wiki, tweet about it. Your help is always appreciated! diff --git a/examples/get_direct_messages.py b/examples/get_direct_messages.py index 5a27a066..fa80865b 100644 --- a/examples/get_direct_messages.py +++ b/examples/get_direct_messages.py @@ -1,17 +1,5 @@ from twython import Twython, TwythonError twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) - get_list = twitter.get_direct_messages() -#Returns All Twitter DM information which is a lot in a list format - -dm_dict = get_list[0] -#Sets get_list to a dictionary, the number in the list is the direct message retrieved -#That means that 0 is the most recent and n-1 is the last DM revieved. -#You can cycle through all the numbers and it will return the text and the sender id of each - -print dm_dict['text'] -#Gets the text from the dictionary - -print dm_dict['sender']['id'] -#Gets the ID of the sender +print(get_list) diff --git a/examples/get_user_timeline.py b/examples/get_user_timeline.py index 0a1f4df5..55ab427f 100644 --- a/examples/get_user_timeline.py +++ b/examples/get_user_timeline.py @@ -7,4 +7,4 @@ except TwythonError as e: print e -print user_timeline +print(user_timeline) diff --git a/setup.py b/setup.py index 11cc2b43..0531221c 100755 --- a/setup.py +++ b/setup.py @@ -31,8 +31,9 @@ keywords='twitter search api tweet twython stream', description='Actively maintained, pure Python wrapper for the \ Twitter API. Supports both normal and streaming Twitter APIs', - long_description=open('README.rst').read() + '\n\n' + - open('HISTORY.rst').read(), + long_description=open('README.md').read() + '\n\n' + + open('HISTORY.md').read(), + long_description_content_type='text/markdown', include_package_data=True, packages=packages, classifiers=[ From 7ce058e6fd3a7307e4ac88e855257a9e99cc7163 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 2 Apr 2020 22:58:15 -0700 Subject: [PATCH 151/165] Resolve issues with Pypi publishing (RST -> MD) and bump to 3.8.0 --- HISTORY.rst => HISTORY.md | 0 setup.py | 12 +++++------- 2 files changed, 5 insertions(+), 7 deletions(-) rename HISTORY.rst => HISTORY.md (100%) diff --git a/HISTORY.rst b/HISTORY.md similarity index 100% rename from HISTORY.rst rename to HISTORY.md diff --git a/setup.py b/setup.py index 0531221c..0c0e7589 100755 --- a/setup.py +++ b/setup.py @@ -8,8 +8,8 @@ except ImportError: from distutils.core import setup -__author__ = 'Ryan McGrath ' -__version__ = '3.7.0' +__author__ = 'Ryan McGrath ' +__version__ = '3.8.0' packages = [ 'twython', @@ -26,13 +26,11 @@ install_requires=['requests>=2.1.0', 'requests_oauthlib>=0.4.0'], author='Ryan McGrath', author_email='ryan@venodesigns.net', - license=open('LICENSE').read(), + license='MIT', url='https://github.com/ryanmcgrath/twython/tree/master', keywords='twitter search api tweet twython stream', - description='Actively maintained, pure Python wrapper for the \ - Twitter API. Supports both normal and streaming Twitter APIs', - long_description=open('README.md').read() + '\n\n' + - open('HISTORY.md').read(), + description='Actively maintained, pure Python wrapper for the Twitter API. Supports both normal and streaming Twitter APIs', + long_description=open('README.md', encoding='utf-8').read() + '\n\n' +open('HISTORY.md', encoding='utf-8').read(), long_description_content_type='text/markdown', include_package_data=True, packages=packages, From ea2979c75f8f771a70617e607b8398809dba8dac Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 2 Apr 2020 23:02:01 -0700 Subject: [PATCH 152/165] Remove this merge as numpy shouldn't be a dependency --- twython/compat.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/twython/compat.py b/twython/compat.py index 2ee7d373..7c049b00 100644 --- a/twython/compat.py +++ b/twython/compat.py @@ -9,7 +9,6 @@ """ import sys -import numpy as np _ver = sys.version_info @@ -30,7 +29,7 @@ str = unicode basestring = basestring - numeric_types = (int, long, float, np.int64, np.float64) + numeric_types = (int, long, float) elif is_py3: @@ -38,4 +37,4 @@ str = str basestring = (str, bytes) - numeric_types = (int, float, np.int64, np.float64) + numeric_types = (int, float) From b8d927df8eeff88717f3b7547461b637de87caf7 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 2 Apr 2020 23:08:52 -0700 Subject: [PATCH 153/165] Kill 2.6/2.7 --- .travis.yml | 3 --- tests/config.py | 5 +---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6ec29583..231b15c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: python python: - - 2.6 - - 2.7 - 3.5 - 3.6 # Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs @@ -26,7 +24,6 @@ env: - ACCESS_TOKEN_B64=U2FsdGVkX18QdBhvMNshM4PGy04tU3HLwKP+nNSoNZHKsvGLjELcWEXN2LIu/T+yngX1vGONf9lo14ElnfS4k7sfhiru8phR4+rZuBVP3bDvC2A6fXJuhuLqNhBrWqg32WQewvxLWDWBoKmnvRHg5b74GHh+IN/12tU0cBF2HK8= install: - pip install -r requirements.txt - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi script: nosetests -v -w tests/ --logging-filter="twython" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing notifications: email: false diff --git a/tests/config.py b/tests/config.py index 8812b811..10ab3ea9 100644 --- a/tests/config.py +++ b/tests/config.py @@ -2,10 +2,7 @@ import os import sys -if sys.version_info[0] == 2 and sys.version_info[1] == 6: - import unittest2 as unittest -else: - import unittest +import unittest app_key = os.environ.get('APP_KEY') app_secret = os.environ.get('APP_SECRET') From cbfec150dfffb63abae8c0e6edd137d02a0fe417 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 2 Apr 2020 23:28:35 -0700 Subject: [PATCH 154/165] Close #486 - given how long ago this was deprecated I'll assume it's not even used --- twython/streaming/types.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/twython/streaming/types.py b/twython/streaming/types.py index 81c5c07f..27c9ea8b 100644 --- a/twython/streaming/types.py +++ b/twython/streaming/types.py @@ -20,26 +20,6 @@ def __init__(self, streamer): self.streamer = streamer self.statuses = TwythonStreamerTypesStatuses(streamer) - def user(self, **params): - """Stream user - - Accepted params found at: - https://dev.twitter.com/docs/api/1.1/get/user - """ - url = 'https://userstream.twitter.com/%s/user.json' \ - % self.streamer.api_version - self.streamer._request(url, params=params) - - def site(self, **params): - """Stream site - - Accepted params found at: - https://dev.twitter.com/docs/api/1.1/get/site - """ - url = 'https://sitestream.twitter.com/%s/site.json' \ - % self.streamer.api_version - self.streamer._request(url, params=params) - class TwythonStreamerTypesStatuses(object): """Class for different statuses endpoints From 74c72f88fddf2623e23601785d8a80c1152989eb Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 2 Apr 2020 23:39:07 -0700 Subject: [PATCH 155/165] Remove old links in docs --- docs/_themes/basicstrap/customsidebar.html | 15 +-------------- docs/conf.py | 4 ++-- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/docs/_themes/basicstrap/customsidebar.html b/docs/_themes/basicstrap/customsidebar.html index ac8c5837..dbeab995 100644 --- a/docs/_themes/basicstrap/customsidebar.html +++ b/docs/_themes/basicstrap/customsidebar.html @@ -1,18 +1,5 @@ -

{{ _('Donate') }}

- -

-Find Twython useful? Consider supporting the author on Gittip: -

- -

- -

-

{{ _('Links') }}

\ No newline at end of file + diff --git a/docs/conf.py b/docs/conf.py index 8f3c3d25..3ffa5b59 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ # built documents. # # The short X.Y version. -version = '3.7.0' +version = '3.8.0' # The full version, including alpha/beta/rc tags. -release = '3.7.0' +release = '3.8.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From ba1110d4b8c6c385bf59fe5318b4aa4726ca00f7 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 3 Apr 2020 02:22:17 -0700 Subject: [PATCH 156/165] Update Manifest to fix installation --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index bcfd3149..79f570ae 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include README.rst HISTORY.rst LICENSE requirements.txt +include README.md HISTORY.md LICENSE requirements.txt recursive-include docs * prune docs/_build From e6b5364d28ba3e7f3688bcdbdb8441e2d33d3ecf Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 3 Apr 2020 02:22:33 -0700 Subject: [PATCH 157/165] Bump version for patching manifest --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bff13d75..ff6dee8b 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.8.0' +__version__ = '3.8.1' packages = [ 'twython', From 02fb35651dd253254ff22c51c591d1bd0c9cf807 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Sat, 4 Apr 2020 16:21:59 -0700 Subject: [PATCH 158/165] Fixes #530, bump version to 3.8.2 --- setup.py | 2 +- twython/streaming/api.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index ff6dee8b..3742f24b 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.8.1' +__version__ = '3.8.2' packages = [ 'twython', diff --git a/twython/streaming/api.py b/twython/streaming/api.py index dd4ae897..6073abbe 100644 --- a/twython/streaming/api.py +++ b/twython/streaming/api.py @@ -86,8 +86,6 @@ def __init__(self, app_key, app_secret, oauth_token, oauth_token_secret, # Set up type methods StreamTypes = TwythonStreamerTypes(self) self.statuses = StreamTypes.statuses - self.user = StreamTypes.user - self.site = StreamTypes.site self.connected = False From 233b20a71057c98cba6f745ecfeada47c76bb67d Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Sun, 12 Apr 2020 05:25:01 +0000 Subject: [PATCH 159/165] Fix deprecation warning regarding invalid escape sequences. --- twython/api.py | 4 ++-- twython/streaming/types.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/twython/api.py b/twython/api.py index bb10db0a..76b6f28f 100644 --- a/twython/api.py +++ b/twython/api.py @@ -430,7 +430,7 @@ def obtain_access_token(self): @staticmethod def construct_api_url(api_url, **params): - """Construct a Twitter API url, encoded, with parameters + r"""Construct a Twitter API url, encoded, with parameters :param api_url: URL of the Twitter API endpoint you are attempting to construct @@ -469,7 +469,7 @@ def search_gen(self, search_query, **params): # pragma: no cover return self.cursor(self.search, q=search_query, **params) def cursor(self, function, return_pages=False, **params): - """Returns a generator for results that match a specified query. + r"""Returns a generator for results that match a specified query. :param function: Instance of a Twython function (Twython.get_home_timeline, Twython.search) diff --git a/twython/streaming/types.py b/twython/streaming/types.py index 27c9ea8b..5042d294 100644 --- a/twython/streaming/types.py +++ b/twython/streaming/types.py @@ -35,7 +35,7 @@ def __init__(self, streamer): self.params = None def filter(self, **params): - """Stream statuses/filter + r"""Stream statuses/filter :param \*\*params: Parameters to send with your stream request @@ -47,7 +47,7 @@ def filter(self, **params): self.streamer._request(url, 'POST', params=params) def sample(self, **params): - """Stream statuses/sample + r"""Stream statuses/sample :param \*\*params: Parameters to send with your stream request @@ -59,7 +59,7 @@ def sample(self, **params): self.streamer._request(url, params=params) def firehose(self, **params): - """Stream statuses/firehose + r"""Stream statuses/firehose :param \*\*params: Parameters to send with your stream request @@ -71,7 +71,7 @@ def firehose(self, **params): self.streamer._request(url, params=params) def set_dynamic_filter(self, **params): - """Set/update statuses/filter + r"""Set/update statuses/filter :param \*\*params: Parameters to send with your stream request From 33f46c087ec6c92dd325101169f3c5c0894d3b7c Mon Sep 17 00:00:00 2001 From: Hannes Date: Fri, 17 Jul 2020 11:50:06 +0200 Subject: [PATCH 160/165] update example for a post involving image editing Python 2 support was dropped from Twython, thanks! In Python3 we actually have to use BytesIO, see https://github.com/python-pillow/Pillow/issues/2205 --- docs/usage/advanced_usage.rst | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index bdb0b231..df3bc485 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -59,15 +59,10 @@ with a status update. .. code-block:: python - # Assume you are working with a JPEG + # Assuming that you are working with a JPEG from PIL import Image - try: - # Python 3 - from io import StringIO - except ImportError: - # Python 2 - from StringIO import StringIO + from io import BytesIO photo = Image.open('/path/to/file/image.jpg') @@ -76,14 +71,13 @@ with a status update. height = int((float(photo.size[1]) * float(wpercent))) photo = photo.resize((basewidth, height), Image.ANTIALIAS) - image_io = StringIO.StringIO() + image_io = BytesIO() photo.save(image_io, format='JPEG') # If you do not seek(0), the image will be at the end of the file and # unable to be read image_io.seek(0) - response = twitter.upload_media(media=image_io) twitter.update_status(status='Checkout this cool image!', media_ids=[response['media_id']]) From 1a54c15a71d054c6421e29cbcefcb9f2186cad49 Mon Sep 17 00:00:00 2001 From: Erik Zscheile Date: Tue, 26 Jan 2021 22:19:13 +0100 Subject: [PATCH 161/165] PEP 479: Change StopIteration handling inside generators --- twython/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/twython/api.py b/twython/api.py index bb10db0a..bd49c49c 100644 --- a/twython/api.py +++ b/twython/api.py @@ -501,7 +501,7 @@ def cursor(self, function, return_pages=False, **params): content = function(**params) if not content: - raise StopIteration + return if hasattr(function, 'iter_key'): results = content.get(function.iter_key) @@ -516,7 +516,7 @@ def cursor(self, function, return_pages=False, **params): if function.iter_mode == 'cursor' and \ content['next_cursor_str'] == '0': - raise StopIteration + return try: if function.iter_mode == 'id': @@ -529,7 +529,7 @@ def cursor(self, function, return_pages=False, **params): params = dict(parse_qsl(next_results.query)) else: # No more results - raise StopIteration + return else: # Twitter gives tweets in reverse chronological order: params['max_id'] = str(int(content[-1]['id_str']) - 1) From 61c1ba9600986d8263af81b8cee954828bb7ce7c Mon Sep 17 00:00:00 2001 From: Erik Zscheile Date: Tue, 26 Jan 2021 22:22:56 +0100 Subject: [PATCH 162/165] PEP 479: add appropriate __future__ tag --- twython/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/twython/api.py b/twython/api.py index bd49c49c..5de6c67c 100644 --- a/twython/api.py +++ b/twython/api.py @@ -9,6 +9,7 @@ dealing with the Twitter API """ +from __future__ import generator_stop import warnings import re From 0b6f372620e864055f9c141c5d72b85a93230d50 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Sat, 6 Feb 2021 17:47:42 -0500 Subject: [PATCH 163/165] Update metadata to describe present support and CI testing --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 3742f24b..39a8e5f0 100755 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ name='twython', version=__version__, install_requires=['requests>=2.1.0', 'requests_oauthlib>=0.4.0'], + python_requires='>=3.5', author='Ryan McGrath', author_email='ryan@venodesigns.net', license='MIT', @@ -41,9 +42,8 @@ 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Communications :: Chat', 'Topic :: Internet', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', From 4be4a504a30ad5e1b2ade398582e6a09e7d97759 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 16 Jul 2021 14:32:48 -0700 Subject: [PATCH 164/165] Push 3.9.0 --- setup.py | 4 ++-- twython/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 3742f24b..307ad8b9 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.8.2' +__version__ = '3.9.0' packages = [ 'twython', @@ -25,7 +25,7 @@ version=__version__, install_requires=['requests>=2.1.0', 'requests_oauthlib>=0.4.0'], author='Ryan McGrath', - author_email='ryan@venodesigns.net', + author_email='ryan@rymc.io', license='MIT', url='https://github.com/ryanmcgrath/twython/tree/master', keywords='twitter search api tweet twython stream', diff --git a/twython/__init__.py b/twython/__init__.py index dc161d13..e01d85dc 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ """ __author__ = 'Ryan McGrath ' -__version__ = '3.7.0' +__version__ = '3.9.0' from .api import Twython from .streaming import TwythonStreamer From 0c405604285364457f3c309969f11ba68163bd05 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 16 Jul 2021 15:33:17 -0700 Subject: [PATCH 165/165] 3.9.1 --- README.md | 8 +++++++- setup.py | 2 +- twython/__init__.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fe0aa1bc..853136e3 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,13 @@ Install Twython via pip: $ pip install twython ``` -Or, if you want the code that is currently on GitHub +If you're on a legacy project that needs Python 2.7 support, you can install the last version of Twython that supported 2.7: + +``` +pip install twython==3.7.0` +``` + +Or, if you want the code that is currently on GitHub: ```bash git clone git://github.com/ryanmcgrath/twython.git diff --git a/setup.py b/setup.py index 4c0cc123..a3600dec 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.9.0' +__version__ = '3.9.1' packages = [ 'twython', diff --git a/twython/__init__.py b/twython/__init__.py index e01d85dc..7d25ef32 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ """ __author__ = 'Ryan McGrath ' -__version__ = '3.9.0' +__version__ = '3.9.1' from .api import Twython from .streaming import TwythonStreamer