Compare commits

...
Sign in to create a new pull request.

137 commits

Author SHA1 Message Date
0c40560428 3.9.1 2021-07-16 15:33:17 -07:00
ecc5f103f4
Merge pull request #532 from tirkarthi/fix-warnings
Fix deprecation warning regarding invalid escape sequences.
2021-07-16 15:28:26 -07:00
f9cf79883d
Merge pull request #541 from altendky/patch-1
Update metadata to describe present support and CI testing
2021-07-16 15:27:06 -07:00
4be4a504a3 Push 3.9.0 2021-07-16 14:32:48 -07:00
e8306c4834
Merge pull request #540 from zseri/fix4pep479
Fix #522 (needed bc of PEP 479)
2021-07-16 13:10:28 -07:00
Kyle Altendorf
0b6f372620
Update metadata to describe present support and CI testing 2021-02-06 17:47:42 -05:00
Erik Zscheile
61c1ba9600 PEP 479: add appropriate __future__ tag 2021-01-26 22:22:56 +01:00
Erik Zscheile
1a54c15a71 PEP 479: Change StopIteration handling inside generators 2021-01-26 22:19:13 +01:00
8e004f5d89
Merge pull request #537 from kannes/patch-1
update example for a post involving image editing
2021-01-14 15:53:25 -08:00
Hannes
33f46c087e
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
2020-07-17 11:50:06 +02:00
Karthikeyan Singaravelan
233b20a710 Fix deprecation warning regarding invalid escape sequences. 2020-04-12 05:25:01 +00:00
02fb35651d
Fixes #530, bump version to 3.8.2 2020-04-04 16:21:59 -07:00
bd734d8775 Merge branch 'master' of github.com:ryanmcgrath/twython 2020-04-03 02:22:42 -07:00
e6b5364d28
Bump version for patching manifest 2020-04-03 02:22:33 -07:00
ba1110d4b8
Update Manifest to fix installation 2020-04-03 02:22:17 -07:00
9ccc576221
Merge pull request #504 from domeniconappo/503-add_response_headers_on_error
Adding response.headers to on_error method in twython.streaming.api #503
2020-04-02 23:49:47 -07:00
74c72f88fd
Remove old links in docs 2020-04-02 23:39:07 -07:00
d096ed45bb Merge branch 'master' of github.com:ryanmcgrath/twython 2020-04-02 23:28:44 -07:00
cbfec150df
Close #486 - given how long ago this was deprecated I'll assume it's not even used 2020-04-02 23:28:35 -07:00
1b004b6155
Merge pull request #513 from jayvdb/patch-1
MANIFEST.in: Add docs, examples and tests
2020-04-02 23:09:24 -07:00
b8d927df8e
Kill 2.6/2.7 2020-04-02 23:08:52 -07:00
ea2979c75f
Remove this merge as numpy shouldn't be a dependency 2020-04-02 23:02:01 -07:00
58587b5e07
Merge pull request #520 from AnnaYasenova/patch-1
Update compat.py
2020-04-02 23:01:03 -07:00
9718f54afe
Merge pull request #514 from jayvdb/py37
Add Python 3.7 support
2020-04-02 23:00:32 -07:00
7ce058e6fd
Resolve issues with Pypi publishing (RST -> MD) and bump to 3.8.0 2020-04-02 22:58:15 -07:00
33fccac46b
Fix this so Pypi publishing works again 2020-04-02 22:48:57 -07:00
Mike Helmick
40a8bd2d15
Merge pull request #527 from timgates42/bugfix/typo_specifically
Fix simple typo: specifcally -> specifically
2020-03-02 09:28:55 -05:00
Tim Gates
1b085180ff
Fix simple typo: specifcally -> specifically
Closes #526
2020-02-28 05:37:05 +11:00
AnnaYasenova
d05fe7516e
Update compat.py 2019-08-20 12:38:20 +03:00
Mike Helmick
7366de80ef
Merge pull request #500 from manuelcortez/master
Added support for the new direct message endpoints
2019-04-10 10:01:10 -04:00
John Vandenberg
a029433247 Add Python 3.7 support
Also remove branch restriction in .travis.yml which
prevents contributors from seeing builds on their forks.
2019-03-30 11:12:19 +07:00
John Vandenberg
554fba4357
MANIFEST.in: Add docs, examples and tests
Closes https://github.com/ryanmcgrath/twython/issues/507
2019-03-10 20:01:14 +07:00
a5e3124f20
Merge pull request #499 from VishvajitP/master
ISSUE-497 : get_oembed_tweet() uses incorrect endpoint
2018-12-02 10:29:30 +09:00
Domenico Nappo
02995d7e88 Adding response.headers to on_error method in twython.streaming.api #503 2018-11-19 14:37:31 +01:00
Manuel Cortez
4f29fd041b Fixed indentation 2018-11-16 05:34:52 -06:00
Manuel Cortez
449807a759 Fixed code styling 2018-09-30 10:53:08 -05:00
Manuel Cortez
96dd5b2897 Added support for delete direct_messages/events/destroy endpoint 2018-09-29 11:09:39 -05:00
Manuel Cortez
a8a0777f72 Added delete methods to Twython's API 2018-09-29 11:09:00 -05:00
Vishvajit Pathak
340fb4ea16 ISSUE-497 : Fixed the api end point in get_oembed_tweet 2018-09-29 15:18:31 +05:30
Manuel Cortez
36fda7ac02 Added new endpoints for direct messages 2018-09-22 12:01:57 -05:00
Mike Helmick
c9e8a46200 Version 3.7.0 2018-05-07 12:52:54 -04:00
Mike Helmick
62e45e9637
Merge pull request #386 from eoso/cursor-fix
Cursor fix
2018-05-07 11:35:25 -04:00
Mike Helmick
677849aa75
Merge pull request #478 from philgyford/patch-1
Removing unused file
2018-01-08 09:36:55 -05:00
Mike Helmick
aa275a4956
Merge pull request #479 from philgyford/fix-html-for-tweet-2
Fix links in tweets when there's a prefix
2018-01-08 09:36:12 -05:00
Phil Gyford
2cfdaaf6e4 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...
2018-01-06 19:53:00 +00:00
Phil Gyford
73982c78f4 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.
2018-01-06 19:03:17 +00:00
Phil Gyford
b009ed30b8
Removing unused file
Sorry, I'm not sure how or why I added this in, but it's not needed by anything!
2018-01-06 17:27:18 +00:00
Mike Helmick
354e31b914
Merge pull request #469 from jvanasco/fix-small_docs
- Cleaned up some docstrings with Sphinx markup and param names.
2017-11-06 12:49:18 -05:00
Mike Helmick
9b46ca5845
Update HISTORY.rst 2017-11-06 12:49:10 -05:00
jonathan vanasco
05fbf6b8b2 - Cleaned up some docstrings with Sphinx markup and param names. 2017-11-06 10:35:26 -05:00
Mike Helmick
3f5de4ad89 Merge pull request #465 from clayadavis/docs
Document api.cursor's return_pages kwarg
2017-10-19 13:43:05 -04:00
Clayton A Davis
db40a1c56c Add doc for cursor's return_pages kwarg 2017-10-18 14:05:12 -04:00
Clayton A Davis
0f64978a08 Fix typo in cursor example 2017-10-18 14:01:34 -04:00
Mike Helmick
1dcd9cc26e Merge pull request #463 from clayadavis/docs
Update links to Twitter docs
2017-10-13 09:42:54 -04:00
Mike Helmick
805590c0d2 Merge pull request #461 from philgyford/fix-media-in-html-for-tweet
Fix URLs for multiple media in html_for_tweet()
2017-10-13 09:42:16 -04:00
Clayton A Davis
8f3db4bc85 Update endpoint docstrings to match new Twitter docs 2017-10-11 21:07:01 -04:00
Clayton A Davis
f3088b0289 Update links to Twitter docs 2017-10-11 14:23:56 -04:00
Phil Gyford
d3f5361f4d Try to fix loading of JSON files in tests on Travis 2017-10-11 18:40:55 +01:00
Phil Gyford
5a008e7e77 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.
2017-10-11 18:27:53 +01:00
Phil Gyford
5c55aa8844 Cut some un-needed data out of the test tweet objects 2017-10-10 12:41:00 +01:00
Phil Gyford
9ccdb48248 Add test for html_for_tweet() for retweets 2017-10-10 12:31:57 +01:00
Phil Gyford
9ade0946b5 Add test for html_for_tweet() for quoted tweets 2017-10-10 12:24:09 +01:00
Phil Gyford
13fd0a8684 Define encoding for tests config file 2017-10-10 11:57:03 +01:00
Mike Helmick
7be654136e Merge pull request #460 from jmdaweb/master
Added create_metadata endpoint
2017-10-09 16:26:01 -04:00
Jose Manuel Delicado
2cb2ed4a31 Twython/api.py: suggested changes in review have been made for pull request 460 2017-10-09 18:11:24 +02:00
Mike Helmick
41a2404b20 Merge pull request #459 from foxmask/patch-1
update the link to the doc of the twitter API
2017-10-09 12:00:47 -04:00
Mike Helmick
c9026247ee Merge pull request #462 from foxmask/master
update the link to the doc of the twitter API
2017-10-09 11:57:33 -04:00
FoxMaSk
89755a8643 update the link to the doc of the twitter API 2017-10-08 08:57:04 +02:00
Phil Gyford
a27efd9da8 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.
2017-10-07 18:38:20 +01:00
Phil Gyford
1511ee7b4d Split test_html_for_tweet() tests into their own file 2017-10-07 18:08:01 +01:00
Jose Manuel Delicado
6fc7b9e038 Added create_metadata endpoint 2017-10-07 18:29:19 +02:00
Jose Manuel Delicado
748d28cc71 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 2017-10-07 18:26:21 +02:00
FoxMaSk
e87b80710d update the link to the doc of the twitter API 2017-10-07 16:46:56 +02:00
Mike Helmick
12e6b34d1d Merge pull request #458 from clayadavis/docs_py3k
Update docs to use py3k
2017-10-06 15:27:23 -04:00
Clayton Davis
c086449818 Update StringIO import for py3k 2017-10-06 14:57:36 -04:00
Clayton Davis
4f1e41a9e5 Update print statements to print() functions 2017-10-06 14:45:25 -04:00
Mike Helmick
0702b4bce1 Merge pull request #456 from philgyford/fix-html-for-tweet
Make html_for_tweet() link a replied-to username
2017-10-04 09:00:44 -04:00
Phil Gyford
0ee9b76b5c 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.
2017-10-03 19:12:02 +01:00
Mike Helmick
97f78fd89b Merge pull request #453 from clayadavis/master
Raise TypeError when cursor is not provided a callable
2017-09-25 13:48:53 -04:00
Clayton A Davis
6166e86807 Add test for cursor creation 2017-09-11 13:52:43 -04:00
Clayton A Davis
c63ed8559e Improve error handling for api.cursor 2017-09-06 18:30:32 -04:00
Mike Helmick
ede941cf1a Version 3.6.0 and update travis python versions 2017-08-23 11:29:20 -04:00
Mike Helmick
8368956f86 Merge pull request #451 from philgyford/fix-447
Improve replacing of entities with links in `html_for_tweet()`
2017-08-23 09:49:13 -04:00
Phil Gyford
6890802b2a 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.
2017-08-22 13:55:22 +01:00
Phil Gyford
b366ab55c3 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
2017-08-22 13:49:40 +01:00
Mike Helmick
5a87fc7d84 Fixes #446, add Python 3 classifier to setup.py 2017-06-16 09:16:22 -04:00
Mike Helmick
574483d870 3.5.0 Release 2017-06-06 10:17:58 -04:00
Mike Helmick
866fb0202a Merge pull request #428 from tushdante/tushdante-video-upload
added media_category and support for STATUS calls
2016-09-30 07:28:38 -04:00
Tushar Bhushan
469432bcf8 - added support for media_category param
- added code to handle empty responses for STATUS calls
2016-09-26 14:22:19 -07:00
Tushar Bhushan
e76a290166 fixed styling issues 2016-09-26 12:51:05 -07:00
Tushar Bhushan
7401adfb64 - 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
2016-09-25 18:03:26 -07:00
Mike Helmick
51be5b7539 Merge pull request #423 from Gageco/patch-1
added get_direct_message example
2016-09-23 09:25:12 -04:00
Mike Helmick
a66268331b Merge pull request #425 from ping/mediaupload_status
Use GET request if the media upload command is STATUS
2016-09-23 09:24:33 -04:00
Mike Helmick
3d3d32f0fc Merge pull request #429 from ping/extended-tweet
Update html_for_tweet to support extended tweets
2016-09-23 09:19:35 -04:00
ping
c57c4bfc34 Update html_for_tweet to support extended tweets 2016-09-20 17:18:21 +08:00
Tushar Bhushan
2c02b622a2 added media_category and support for STATUS calls 2016-09-14 15:35:51 -07:00
Mike Helmick
975095d8d2 Merge pull request #426 from philgyford/bugs/422
Fix upload_video "string argument detected, got 'bytes'" bug.
2016-08-18 11:23:14 -04:00
Phil Gyford
1b20f8f0f9 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
2016-08-17 14:18:59 +01:00
ping
f7ddbcf414 Use GET request if the media upload command is STATUS 2016-06-22 22:20:26 +08:00
Gage Coprivnicar
f91da7cadf 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
2016-06-12 16:42:25 -06:00
Mike Helmick
2faa84629b Merge pull request #415 from philgyford/symbols-in-html-for-tweet
Link $IBM-style symbols in tweets in html_for_tweet()
2016-04-30 05:33:14 -04:00
Mike Helmick
86f878aad8
Fixes #417, update ReadTheDocs TLD in README 2016-04-30 05:31:13 -04:00
Mike Helmick
3f6700373f
3.4.0 release, update version and history 2016-04-30 05:22:26 -04:00
Mike Helmick
6cd13d772e Merge pull request #411 from ping/quoted-status-patch2
Fix issue where is_quote_status is true but there is no quoted_status
2016-04-30 05:14:31 -04:00
Phil Gyford
a1640f4a17 Link $IBM-style symbols in tweets in html_for_tweet()
Fixes #412
2016-04-22 14:22:32 +01:00
ping
99e9cebdc3 Fix issue where is_quote_status is true but there is no quoted_status 2016-03-29 10:44:04 +08:00
Mike Helmick
885051acdc Merge pull request #397 from ping/quoted_status_patch
Fix html_for_tweet for pre-quote implementation
2015-10-12 13:32:52 -04:00
ping
ff4655c314 Fix html_for_tweet for pre-quote implementation tweet html 2015-10-08 11:31:07 +08:00
Mike Helmick
c59de5c1df Merge pull request #396 from akarambir/upload-video-endpoint
Add chunked video upload functionality
2015-10-06 15:29:51 -04:00
Karambir Singh Nain
2cca895079 Add video upload endpoint 2015-09-29 20:57:47 +05:30
Mike Helmick
00d9d6a766 Merge pull request #357 from derek-dchu/master
Remove redundant checking for oauth_token & oauth_token_secret
2015-09-12 07:45:57 -04:00
Mike Helmick
594f02f401 Merge pull request #390 from ping/html-for-tweet-patch-389
Fix html_for_tweet when a hashtag/mention is a substring of another
2015-08-18 11:38:48 -04:00
Mike Helmick
ff6444cb32 Merge pull request #391 from ping/quoted_status_in_html_for_tweet
Add support for quoted_status in html_for_tweet()
2015-08-18 11:37:11 -04:00
ping
62d51c7431 Add support for quoted_status in html_for_tweet() 2015-08-17 14:22:32 +08:00
ping
2ea45abb73 Fix html_for_tweet when a hashtag/mention is a substring of another 2015-08-15 19:43:59 +08:00
Mike Helmick
26ea0f3ae0 Merge pull request #388 from mobolic/fka-wcdolphin
Update Cory Dolphin's Github username in AUTHORS.
2015-08-14 09:53:34 -04:00
Martey Dodoo
4a362a42aa 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
2015-08-13 21:56:46 -04:00
Ethan Baer
0b3df413d8 added urlsplit import for use in api.cursor function 2015-07-31 15:07:25 -05:00
Ethan Baer
a875f270a8 fixed cursor function code to return next pages when funciton iter_mode is 'id' 2015-07-31 15:03:26 -05:00
Mike Helmick
faee7ea3ff Update AUTHORS.rst, Update HISTORY.rst, Update version
[ci skip]
2015-07-18 10:32:30 -04:00
Mike Helmick
34db3a58ad Update README.rst
[ci skip]
2015-07-18 10:11:04 -04:00
Mike Helmick
1905e58ee5 Merge pull request #384 from sinistersnare/master
Have lookup_user GET and not POST
2015-07-15 11:55:40 -04:00
Davis Silverman
dff85ace49 Have lookup_user GET and not POST 2015-07-13 15:20:21 -04:00
Mike Helmick
97771bf15c Merge pull request #382 from 7kfpun/master
Add missing commas
2015-07-13 11:03:35 -04:00
Mike Helmick
b1227f6a03 Merge pull request #383 from davidjb/patch-1
Update advanced example documentation for uploading images
2015-07-13 11:02:02 -04:00
David Beitey
1f3eab6a37 Update uploading image advanced examples
Previously, these examples were using deprecated API endpoints.  This updates the examples accordingly.
2015-07-08 20:26:05 +10:00
kf
e68ba98c8f missing commas 2015-07-07 22:16:42 +08:00
Mike Helmick
35bd233d8a Merge pull request #381 from wjt/patch-1
special_functions.rst: typo: excepts → accepts
2015-06-18 13:23:03 -04:00
Will Thompson
4e6963d8cc special_functions.rst: typo: excepts → accepts 2015-06-17 16:27:31 +01:00
Mike Helmick
2f0508bcdf Merge pull request #376 from Oire/bugs/375
Corrected the docs URL for uploading media
2015-04-08 17:59:00 -04:00
Andre Polykanine A.K.A. Menelion Elensúlë
4c213ea10d Corrected the docs URL for uploading media, fixes #375 2015-04-09 00:55:24 +03:00
Mike Helmick
18e845d00d Merge pull request #367 from jkeenest/master
search_gen in Advanced usage deprecated
2015-03-30 10:02:32 -04:00
Mike Helmick
5187d00d04 Merge pull request #373 from bbertka/master
Added dynamic filtering to streamer
2015-03-30 10:02:06 -04:00
Ben Bertka
6d1c82b594 Fixed indents for dynamic filtering in types.py 2015-03-24 11:36:08 -07:00
Ben Bertka
e0a2dfbbb0 Added dynamic filtering 2015-03-24 11:00:49 -07:00
Jeremy Keen
db738cf41e search_gen in Advanced usage deprecated
Updated advanced usage documentation to use cursor instead of search_gen
2015-02-13 12:05:28 -08:00
Derek Hu
8eb8c2dd55 Remove redundant checking for oauth_token & oauth_token_secret 2014-11-19 01:09:02 -05:00
38 changed files with 2070 additions and 603 deletions

View file

@ -1,8 +1,13 @@
language: python
python:
- 2.6
- 2.7
- 3.3
- 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=
@ -17,15 +22,11 @@ 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
notifications:
email: false
branches:
only:
- master
after_success:
- coveralls
before_script:

View file

@ -44,7 +44,7 @@ Patches and Suggestions
- `Oleg Anashkin <https://github.com/extesy>`_, streaming ``handlers`` functionality
- `Luis Alberto Santana <https://github.com/jackboot7`_, Added auth_endpoint parameter for cases when the right permissions weren't being shown on authenticate step
- `Cory Benfield <https://github.com/Lukasa>`_, Fixed issue where Twython was unnecessarily disabling compression
- `Cory Dolphin <https://github.com/wcdolphin>`_, Added retry_after attribute to TwythonRateLimitError
- `Cory Dolphin <https://github.com/corydolphin>`_, Added retry_after attribute to TwythonRateLimitError
- `Natan L <https://github.com/kuyan>`_, Fixed typo in documentation
- `Cash Costello <https://github.com/cash>`_, Moved tests to use `responsoes`, fixed typos in documentation
- `Joe Cabrera <https://github.com/greedo>`_, PEP 8 contributions
@ -55,3 +55,11 @@ Patches and Suggestions
- `Donne Martin <https://github.com/donnemartin>`_, Fixed typos in `README.rst`
- `Diego Allen <https://github.com/dalleng>`_, Add missing comma in documentation code snippet
- `Ben McGinnes <https://github.com/Hasimir>`_, Added mute API endpoints, a couple of examples, random bits.
- `Davis Silverman <https://github.com/sinistersnare>`_, Fixed endpoint HTTP method
- `David Beitey <https://github.com/sinistersnare>`_, Fixed documentation typos
- `7kfpun <https://github.com/7kfpun>`_, Fixed documentation typos
- `Will Thompson <https://github.com/wjt>`_, Fixed documentation typos
- `Andre Polykanine <https://github.com/Oire>`_, Fixed documentation typos
- `Ben Bertka <https://github.com/bbertka>`_, Added dynamic filtering in streamer
- `Takahashi Shuuji <https://github.com/shuuji3>`_, Fixed documentation typos
- `Jeremy Keen <https://github.com/jkeenest>`_, Updated documentation example

View file

@ -1,14 +1,39 @@
.. :changelog:
# History
History
-------
## 3.8.0 (2020-04-02)
- Bump release with latest patches from GitHub.
- Fix Direct Messages with patches from @manuelcortez.
3.3.0 (2014-xx-xx)
++++++++++++++++++
## 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)
- 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()`
- 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
- 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
- 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
@ -21,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.
@ -49,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
@ -70,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
@ -82,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)
@ -95,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
@ -131,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

View file

@ -1 +1,9 @@
include README.rst HISTORY.rst LICENSE requirements.txt
include README.md HISTORY.md LICENSE requirements.txt
recursive-include docs *
prune docs/_build
recursive-include examples *.py
recursive-include tests *.py
recursive-include tests/tweets *.json

169
README.md Normal file
View file

@ -0,0 +1,169 @@
# Twython
<a href="https://pypi.python.org/pypi/twython"><img src="https://img.shields.io/pypi/v/twython.svg?style=flat-square"></a>
<a href="https://pypi.python.org/pypi/twython"><img src="https://img.shields.io/pypi/dw/twython.svg?style=flat-square"></a>
<a href="https://travis-ci.org/ryanmcgrath/twython"><img src="https://img.shields.io/travis/ryanmcgrath/twython.svg?style=flat-square"></a>
<a href="https://coveralls.io/r/ryanmcgrath/twython?branch=master"><img src="https://img.shields.io/coveralls/ryanmcgrath/twython/master.svg?style=flat-square"></a>
`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
```
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
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!

View file

@ -1,219 +0,0 @@
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
``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 <https://dev.twitter.com/docs/api/1.1>`_
- 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 <http://www.pip-installer.org/>`_
.. code-block:: bash
$ pip install twython
or, with `easy_install <http://pypi.python.org/pypi/setuptools>`_
.. code-block:: bash
$ easy_install twython
But, hey... `that's up to you <http://www.pip-installer.org/en/latest/other-tools.html#pip-compared-to-easy-install>`_.
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.org/en/latest/
Starting Out
------------
First, you'll want to head over to https://dev.twitter.com/apps 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 <https://twython.readthedocs.org/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
.. code-block:: python
from twython import Twython
twitter = Twython(APP_KEY, APP_SECRET,
OAUTH_TOKEN, OAUTH_TOKEN_SECRET)
Authenticated Users Home Timeline
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Documentation: https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline
.. code-block:: python
twitter.get_home_timeline()
Updating Status
~~~~~~~~~~~~~~~
This method makes use of dynamic arguments, `read more about them <https://twython.readthedocs.org/en/latest/usage/starting_out.html#dynamic-function-arguments>`_
Documentation: https://dev.twitter.com/docs/api/1/post/statuses/update
.. code-block:: python
twitter.update_status(status='See how easy using Twython is!')
Searching
~~~~~~~~~
https://dev.twitter.com/docs/api/1.1/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 <https://twython.readthedocs.org/en/latest/usage/advanced_usage.html>`_
- `Streaming with Twython <https://twython.readthedocs.org/en/latest/usage/streaming_api.html>`_
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 <https://github.com/ryanmcgrath/twython/blob/master/HISTORY.rst#300-2013-06-18>`_ 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 <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!

View file

@ -1,18 +1,5 @@
<h4>{{ _('Donate') }}</h4>
<p>
Find Twython useful? Consider supporting the author on <a href="https://www.gittip.com/ryanmcgrath/">Gittip</a>:
</p>
<p>
<iframe style="border: 0; margin: 0; padding: 0;"
src="https://www.gittip.com/ryanmcgrath/widget.html"
width="48pt" height="20pt"></iframe>
</p>
<h4>{{ _('Links') }}</h4>
<ul>
<li><a href="https://crate.io/packages/twython/">Twython on Crate</a></li>
<li><a href="https://github.com/ryanmcgrath/twython">Twython on GitHub</a></li>
<li><a href="https://github.com/ryanmcgrath/twython/issues">Bug Tracker</a></li>
</ul>
</ul>

View file

@ -50,9 +50,9 @@ copyright = u'2013, Ryan McGrath'
# built documents.
#
# The short X.Y version.
version = '3.2.0'
version = '3.8.0'
# The full version, including alpha/beta/rc tags.
release = '3.2.0'
release = '3.8.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View file

@ -16,7 +16,7 @@ Features
- Twitter lists
- Timelines
- Direct Messages
- and anything found in `the Twitter API docs <https://dev.twitter.com/docs/api/1.1>`_.
- and anything found in `the Twitter API docs <https://developer.twitter.com/en/docs>`_.
- Image Uploading:
- Update user status with an image
- Change user avatar

View file

@ -16,30 +16,53 @@ 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
--------------------------
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://developer.twitter.com/en/docs/api-reference-index
* https://developer.twitter.com/en/docs/media/upload-media/overview
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://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
--------------------------------------
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
# Assume you are working with a JPEG
# Assuming that you are working with a JPEG
from PIL import Image
from StringIO import StringIO
from io import BytesIO
photo = Image.open('/path/to/file/image.jpg')
@ -48,14 +71,15 @@ Posting a Status with an Editing Image
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)
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
@ -67,9 +91,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.)
-----------------------------------------------
@ -90,8 +118,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:
@ -107,8 +135,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):
@ -128,8 +156,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
@ -142,7 +170,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()

View file

@ -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 <dynamicargexplaination>`
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
@ -80,7 +80,7 @@ Documentation: https://dev.twitter.com/docs/api/1.1/get/search/tweets
.. _dynamicargexplaination:
.. important:: To help explain :ref:`dynamic function arguments <dynamicfunctionarguments>` a little more, you can see that the previous call used the keyword argument ``q``, that is because Twitter specifies in their `search documentation <https://dev.twitter.com/docs/api/1.1/get/search/tweets>`_ 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 <dynamicfunctionarguments>` a little more, you can see that the previous call used the keyword argument ``q``, that is because Twitter specifies in their `search documentation <https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets>`_ 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

View file

@ -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,15 +39,28 @@ The New Way
results = twitter.cursor(twitter.search, q='twitter')
for result in results:
print result['id_str']
print(result['id_str'])
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']
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
@ -66,17 +79,17 @@ 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.
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:
<a href="http://t.co/FCmXyI6VHd" class="twython-url">google.com</a> is a <a href="https://twitter.com/search?q=%23cool" class="twython-hashtag">#cool</a> site, lol! <a href="https://twitter.com/mikehelmick" class="twython-mention">@mikehelmick</a> shd <a href="https://twitter.com/search?q=%23checkitout" class="twython-hashtag">#checkitout</a>. Love, <a href="https://twitter.com/__twython__" class="twython-mention">@__twython__</a> <a href="https://t.co/67pwRvY6z9" class="twython-url">github.com</a> <a href="http://t.co/N6InAO4B71" class="twython-media">pic.twitter.com/N6InAO4B71</a>
<a href="http://t.co/FCmXyI6VHd" class="twython-url">google.com</a> is <a href="https://twitter.com/search?q=%23cool" class="twython-hashtag">#cool</a>, lol! <a href="https://twitter.com/mikehelmick" class="twython-mention">@mikehelmick</a> shd <a href="https://twitter.com/search?q=%23checkitout" class="twython-hashtag">#checkitout</a>. Love, <a href="https://twitter.com/__twython__" class="twython-mention">@__twython__</a> <a href="https://twitter.com/?q=%24IBM" class="twython-symbol">$IBM</a> <a href="https://t.co/67pwRvY6z9" class="twython-url">github.com</a> <a href="http://t.co/N6InAO4B71" class="twython-media">pic.twitter.com/N6InAO4B71</a>
.. 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,8 +97,9 @@ 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 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)

View file

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

View file

@ -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 <oauth1>` and find out how!
@ -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!

View file

@ -0,0 +1,5 @@
from twython import Twython, TwythonError
twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET)
get_list = twitter.get_direct_messages()
print(get_list)

View file

@ -7,4 +7,4 @@ try:
except TwythonError as e:
print e
print user_timeline
print(user_timeline)

View file

@ -8,8 +8,8 @@ try:
except ImportError:
from distutils.core import setup
__author__ = 'Ryan McGrath <ryan@venodesigns.net>'
__version__ = '3.2.0'
__author__ = 'Ryan McGrath <ryan@rymc.io>'
__version__ = '3.9.1'
packages = [
'twython',
@ -24,15 +24,15 @@ setup(
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=open('LICENSE').read(),
author_email='ryan@rymc.io',
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.rst').read() + '\n\n' +
open('HISTORY.rst').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,
classifiers=[
@ -41,6 +41,11 @@ setup(
'License :: OSI Approved :: MIT License',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Communications :: Chat',
'Topic :: Internet'
'Topic :: Internet',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
]
)

View file

@ -1,10 +1,8 @@
# -*- coding: utf-8 -*-
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')
@ -26,5 +24,3 @@ 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_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 = '<a href="http://t.co/FCmXyI6VHd" class="twython-url">google.com</a> is a <a href="https://twitter.com/search?q=%23cool" class="twython-hashtag">#cool</a> site, lol! <a href="https://twitter.com/mikehelmick" class="twython-mention">@mikehelmick</a> shd <a href="https://twitter.com/search?q=%23checkitout" class="twython-hashtag">#checkitout</a>. Love, <a href="https://twitter.com/__twython__" class="twython-mention">@__twython__</a> <a href="https://t.co/67pwRvY6z9" class="twython-url">github.com</a> <a href="http://t.co/N6InAO4B71" class="twython-media">pic.twitter.com/N6InAO4B71</a>'

View file

@ -1,8 +1,7 @@
# -*- coding: utf-8 -*-
from twython import Twython, TwythonError, TwythonAuthError, TwythonRateLimitError
from .config import (
test_tweet_object, test_tweet_html, unittest
)
from .config import unittest
import responses
import requests
@ -299,21 +298,15 @@ class TwythonAPITestCase(unittest.TestCase):
"""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_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)
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)
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)
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)

View file

@ -4,7 +4,7 @@ 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, unittest
access_token, unittest
)
import time

View file

@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
import json
import os
from twython import Twython, TwythonError
from .config import unittest
class TestHtmlForTweetTestCase(unittest.TestCase):
def setUp(self):
self.api = Twython('', '', '', '')
def load_tweet(self, name):
f = open(os.path.join(
os.path.dirname(__file__),
'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_object = self.load_tweet('basic')
tweet_text = self.api.html_for_tweet(tweet_object)
self.assertEqual(tweet_text,
'<a href="http://t.co/FCmXyI6VHd" class="twython-url">google.com</a> is a <a href="https://twitter.com/search?q=%23cool" class="twython-hashtag">#cool</a> site, lol! <a href="https://twitter.com/mikehelmick" class="twython-mention">@mikehelmick</a> shd <a href="https://twitter.com/search?q=%23checkitout" class="twython-hashtag">#checkitout</a>. Love, <a href="https://twitter.com/__twython__" class="twython-mention">@__twython__</a> <a href="https://t.co/67pwRvY6z9" class="twython-url">github.com</a> <a href="http://t.co/N6InAO4B71" class="twython-media">pic.twitter.com/N6InAO4B71</a>')
def test_reply(self):
"""Test HTML for Tweet links the replied-to username."""
tweet_object = self.load_tweet('reply')
tweet_text = self.api.html_for_tweet(tweet_object)
self.assertEqual(tweet_text,
u'<span class="twython-tweet-prefix"><a href="https://twitter.com/philgyford" class="twython-mention">@philgyford</a> </span>Heres a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland!<span class="twython-tweet-suffix"> https://t.co/tzhyk2QWSr</span>')
def test_expanded_url(self):
"""Test using expanded url in HTML for Tweet displays full urls"""
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_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_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 <a href="https://t.co/W0uArTMk9N" class="twython-url">buff.ly/2sEhrgO</a> #5G #innovation via @5GWorldSeries <a href="https://t.co/W0uArTMk9N" class="twython-url">buff.ly/2sEhrgO</a>')
def test_symbols(self):
tweet_object = self.load_tweet('symbols')
tweet_text = self.api.html_for_tweet(tweet_object)
# Should only link symbols listed in entities:
self.assertTrue('<a href="https://twitter.com/search?q=%24AAPL" class="twython-symbol">$AAPL</a>' in tweet_text)
self.assertTrue('<a href="https://twitter.com/search?q=%24ANOTHER" class="twython-symbol">$ANOTHER</a>' not in tweet_text)
def test_no_symbols(self):
"""Should still work if tweet object has no symbols list"""
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)
def test_compatmode(self):
tweet_object = self.load_tweet('compat')
tweet_text = self.api.html_for_tweet(tweet_object)
# link to compat web status link
self.assertTrue(
u'<a href="https://t.co/SRmsuks2ru" class="twython-url">twitter.com/i/web/status/7…</a>' in tweet_text)
def test_extendedmode(self):
tweet_object = self.load_tweet('extended')
tweet_text = self.api.html_for_tweet(tweet_object)
# full tweet rendered with suffix
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.<span class="twython-tweet-suffix"> <a href="https://t.co/I9pUC0NdZC" class="twython-media">pic.twitter.com/I9pUC0NdZC</a></span>')
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 = 2200
tweet_object = self.load_tweet('entities_with_prefix')
tweet_text = self.api.html_for_tweet(tweet_object)
self.assertEqual(tweet_text,
u'<span class="twython-tweet-prefix"><a href="https://twitter.com/philgyford" class="twython-mention">@philgyford</a> </span>This is a test for <a href="https://twitter.com/visionphil" class="twython-mention">@visionphil</a> that includes a link <a href="https://t.co/sKw4J3A8SZ" class="twython-url">example.org</a> and <a href="https://twitter.com/search?q=%23hashtag" class="twython-hashtag">#hashtag</a> and X for good measure AND that is longer than 140 characters. <a href="https://t.co/jnQdy7Zg7u" class="twython-url">example.com</a>')
def test_media(self):
tweet_object = self.load_tweet('media')
tweet_text = self.api.html_for_tweet(tweet_object)
self.assertEqual(
u"""I made some D3.js charts showing the years covered by books in a series compared to their publishing dates <a href="https://t.co/2yUmmn3TOc" class="twython-url">gyford.com/phil/writing/2\u2026</a><span class="twython-tweet-suffix"> <a href="https://t.co/OwNc6uJklg" class="twython-media">pic.twitter.com/OwNc6uJklg</a></span>""",
tweet_text)
def test_quoted(self):
"With expand_quoted_status=True it should include a quoted tweet."
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. <a href="https://t.co/3neKzof0gT" class="twython-url">twitter.com/philgyford/sta\u2026</a><blockquote class="twython-quote">The quoted tweet text.<cite><a href="https://twitter.com/philgyford/status/917699069916729344"><span class="twython-quote-user-name">Phil Gyford</span><span class="twython-quote-user-screenname">@philgyford</span></a></cite></blockquote>""",
tweet_text)
def test_retweet(self):
"With expand_quoted_status=True it should include a quoted tweet."
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.""",
tweet_text)

170
tests/tweets/basic.json Normal file
View file

@ -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":"<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>",
"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
}

51
tests/tweets/compat.json Normal file
View file

@ -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":"<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>",
"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
}

View file

@ -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 X 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":"<a href=\"http://tapbots.com/software/tweetbot/mac\" rel=\"nofollow\">Tweetbot for Mac</a>",
"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"
}

204
tests/tweets/extended.json Normal file
View file

@ -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":"<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>",
"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"
}
]
}
}

View file

@ -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"
}

205
tests/tweets/media.json Normal file
View file

@ -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":"<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>",
"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"
}
]
}
}

94
tests/tweets/quoted.json Normal file
View file

@ -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":"<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>",
"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":"<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>",
"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
}

62
tests/tweets/reply.json Normal file
View file

@ -0,0 +1,62 @@
{
"display_text_range":[
12,
114
],
"in_reply_to_status_id_str":"742374355531923456",
"source":"<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>",
"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
}

91
tests/tweets/retweet.json Normal file
View file

@ -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":"<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>",
"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
}

61
tests/tweets/symbols.json Normal file
View file

@ -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":"<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>"
}

View file

@ -19,7 +19,7 @@ Questions, comments? ryan@venodesigns.net
"""
__author__ = 'Ryan McGrath <ryan@venodesigns.net>'
__version__ = '3.2.0'
__version__ = '3.9.1'
from .api import Twython
from .streaming import TwythonStreamer

View file

@ -17,6 +17,6 @@ only TwythonDeprecationWarnings.
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

View file

@ -9,7 +9,9 @@ Twitter Authentication, and miscellaneous methods that are useful when
dealing with the Twitter API
"""
from __future__ import generator_stop
import warnings
import re
import requests
from requests.auth import HTTPBasicAuth
@ -18,6 +20,7 @@ from requests_oauthlib import OAuth1, OAuth2
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
@ -102,15 +105,10 @@ class Twython(EndpointsMixin, object):
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,
@ -138,13 +136,17 @@ class Twython(EndpointsMixin, object):
def __repr__(self):
return '<Twython: %s>' % (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)
params, files = _transparent_params(params)
if isinstance(params, dict) and json_encoded is False:
params, files = _transparent_params(params)
else:
params = params
files = list()
requests_args = {}
for k, v in self.client_args.items():
@ -152,11 +154,16 @@ class Twython(EndpointsMixin, object):
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"
if json_encoded:
data_key = 'json'
else:
data_key = 'data'
requests_args.update({
'data': params,
data_key: params,
'files': files,
})
try:
@ -196,12 +203,16 @@ class Twython(EndpointsMixin, object):
error_message,
error_code=response.status_code,
retry_after=response.headers.get('X-Rate-Limit-Reset'))
content = ''
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.')
if response.content != '':
raise TwythonError('Response was not valid JSON. \
Unable to decode.')
return content
@ -225,14 +236,14 @@ class Twython(EndpointsMixin, object):
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
(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
@ -241,6 +252,9 @@ class Twython(EndpointsMixin, object):
: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
"""
@ -256,7 +270,7 @@ class Twython(EndpointsMixin, object):
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
@ -264,9 +278,13 @@ class Twython(EndpointsMixin, object):
"""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 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
@ -413,7 +431,7 @@ class Twython(EndpointsMixin, object):
@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
@ -452,7 +470,7 @@ class Twython(EndpointsMixin, object):
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)
@ -471,6 +489,11 @@ class Twython(EndpointsMixin, object):
>>> 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__)
@ -479,7 +502,7 @@ class Twython(EndpointsMixin, object):
content = function(**params)
if not content:
raise StopIteration
return
if hasattr(function, 'iter_key'):
results = content.get(function.iter_key)
@ -494,23 +517,31 @@ class Twython(EndpointsMixin, object):
if function.iter_mode == 'cursor' and \
content['next_cursor_str'] == '0':
raise StopIteration
return
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
return
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):
@ -528,8 +559,8 @@ class Twython(EndpointsMixin, object):
return str(text)
@staticmethod
def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False):
"""Return HTML for a tweet (urls, mentions, hashtags replaced with links)
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, symbols replaced with links)
:param tweet: Tweet object from received from Twitter API
:param use_display_url: Use display URL to represent link
@ -545,54 +576,134 @@ class Twython(EndpointsMixin, object):
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']
# We'll put all the bits of replacement HTML and their starts/ends
# in this list:
entities = []
# Mentions
for entity in entities['user_mentions']:
start, end = entity['indices'][0], entity['indices'][1]
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 = '<a href="https://twitter.com/%(screen_name)s" class="twython-mention">@%(screen_name)s</a>'
text = text.replace(tweet['text'][start:end],
mention_html % {'screen_name': entity['screen_name']})
mention_html = '<a href="https://twitter.com/%(screen_name)s" class="twython-mention">@%(screen_name)s</a>' % {'screen_name': entity['screen_name']}
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
# display_text, into a link:
sub_expr = r'(?<!>)' + orig_tweet_text[temp['start']:temp['end']] + '(?!</a>)'
prefix_text = re.sub(sub_expr, mention_html, prefix_text)
# Hashtags
for entity in entities['hashtags']:
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] - display_text_start
temp['end'] = entity['indices'][1] - display_text_start
hashtag_html = '<a href="https://twitter.com/search?q=%%23%(hashtag)s" class="twython-hashtag">#%(hashtag)s</a>'
text = text.replace(tweet['text'][start:end], hashtag_html % {'hashtag': entity['text']})
url_html = '<a href="https://twitter.com/search?q=%%23%(hashtag)s" class="twython-hashtag">#%(hashtag)s</a>' % {'hashtag': entity['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']
temp['replacement'] = url_html
entities.append(temp)
url_html = '<a href="%s" class="twython-url">%s</a>'
text = text.replace(tweet['text'][start:end],
url_html % (entity['url'], shown_url))
# Symbols
if 'symbols' in tweet['entities']:
for entity in tweet['entities']['symbols']:
temp = {}
temp['start'] = entity['indices'][0] - display_text_start
temp['end'] = entity['indices'][1] - display_text_start
# 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:
url_html = '<a href="https://twitter.com/search?q=%%24%(symbol)s" class="twython-symbol">$%(symbol)s</a>' % {'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] - 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']
elif use_expanded_url and entity.get('expanded_url'):
shown_url = entity['expanded_url']
else:
shown_url = entity['url']
url_html = '<a href="%s" class="twython-media">%s</a>'
text = text.replace(tweet['text'][start:end],
url_html % (entity['url'], shown_url))
url_html = '<a href="%s" class="twython-url">%s</a>' % (entity['url'], shown_url)
return text
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 = '<a href="%s" class="twython-media">%s</a>' % (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:
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'):
quoted_status = tweet['quoted_status']
quote_text += '<blockquote class="twython-quote">%(quote)s<cite><a href="%(quote_tweet_link)s">' \
'<span class="twython-quote-user-name">%(quote_user_name)s</span>' \
'<span class="twython-quote-user-screenname">@%(quote_user_screen_name)s</span></a>' \
'</cite></blockquote>' % \
{'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 '%(prefix)s%(display)s%(suffix)s%(quote)s' % {
'prefix': '<span class="twython-tweet-prefix">%s</span>' % prefix_text if prefix_text else '',
'display': display_text,
'suffix': '<span class="twython-tweet-suffix">%s</span>' % suffix_text if suffix_text else '',
'quote': quote_text
}

View file

@ -25,7 +25,7 @@ except ImportError:
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 @@ if is_py2:
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)

File diff suppressed because it is too large Load diff

View file

@ -86,8 +86,6 @@ class TwythonStreamer(object):
# Set up type methods
StreamTypes = TwythonStreamerTypes(self)
self.statuses = StreamTypes.statuses
self.user = StreamTypes.user
self.site = StreamTypes.site
self.connected = False
@ -125,7 +123,7 @@ class TwythonStreamer(object):
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:
@ -169,16 +167,16 @@ class TwythonStreamer(object):
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
"""
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 +187,9 @@ class TwythonStreamer(object):
: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

View file

@ -20,64 +20,46 @@ class TwythonStreamerTypes(object):
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
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):
self.streamer = streamer
self.params = None
def filter(self, **params):
"""Stream statuses/filter
r"""Stream 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
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
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
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
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
@ -87,3 +69,20 @@ class TwythonStreamerTypesStatuses(object):
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):
r"""Set/update statuses/filter
:param \*\*params: Parameters to send with your stream request
Accepted params found at:
https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/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)