├── .coveragerc ├── .gitignore ├── .gitmodules ├── .travis.yml ├── AUTHORS.rst ├── HISTORY.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── _static │ └── custom.css ├── _themes │ └── basicstrap │ │ ├── customsidebar.html │ │ ├── layout.html │ │ ├── search.html │ │ ├── searchbox.html │ │ ├── searchresults.html │ │ ├── static │ │ ├── basicstrap.css_t │ │ ├── css │ │ │ ├── basicstrap-base.css │ │ │ ├── bootstrap-responsive.css │ │ │ ├── bootstrap-responsive.min.css │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.min.css │ │ │ ├── bootswatch-cerulean.css │ │ │ ├── font-awesome-ie7.min.css │ │ │ ├── font-awesome.css │ │ │ └── font-awesome.min.css │ │ ├── font │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ │ ├── img │ │ │ ├── glyphicons-halflings-white.png │ │ │ └── glyphicons-halflings.png │ │ └── js │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.min.js │ │ │ └── jquery.min.js │ │ └── theme.conf ├── api.rst ├── conf.py ├── index.rst ├── make.bat └── usage │ ├── advanced_usage.rst │ ├── basic_usage.rst │ ├── install.rst │ ├── special_functions.rst │ ├── starting_out.rst │ └── streaming_api.rst ├── examples ├── follow_user.py ├── get_direct_messages.py ├── get_user_timeline.py ├── search_results.py ├── stream.py ├── update_profile_image.py └── update_status.py ├── requirements.txt ├── setup.py ├── tests ├── __init__.py ├── config.py ├── test_auth.py ├── test_core.py ├── test_endpoints.py ├── test_html_for_tweet.py ├── test_streaming.py └── tweets │ ├── basic.json │ ├── compat.json │ ├── entities_with_prefix.json │ ├── extended.json │ ├── identical_urls.json │ ├── media.json │ ├── quoted.json │ ├── reply.json │ ├── retweet.json │ └── symbols.json └── twython ├── __init__.py ├── advisory.py ├── api.py ├── compat.py ├── endpoints.py ├── exceptions.py ├── helpers.py └── streaming ├── __init__.py ├── api.py └── types.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | ../twython/advisory.py 4 | ../twython/compat.py 5 | 6 | [report] 7 | exclude_lines = 8 | pragma: no cover 9 | 10 | def __repr__ 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | *.DS_Store 38 | 39 | docs/_build 40 | 41 | test.py 42 | 43 | .venv 44 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "twython-django"] 2 | path = twython-django 3 | url = git://github.com/ryanmcgrath/twython-django.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 3.5 4 | - 3.6 5 | # Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs 6 | matrix: 7 | include: 8 | - python: 3.7 9 | dist: xenial 10 | sudo: true 11 | env: 12 | global: 13 | - secure: USjLDneiXlVvEjkUVqTt+LBi0XJ4QhkRcJzqVXA9gEau1NTjAkNTPmHjUbOygp0dkfoV0uWrZKCw6fL1g+HJgWl0vHeHzcNl4mUkA+OwkGFHgaeIhvUfnyyJA8P3Zm21XHC+ehzMpEFN5fVNNhREjnRj+CXMc0FgA6knwBRobu4= 14 | - secure: HFTL8UN1GkY6/GqygpgzWpYdWvhXRN8uTO//AgGzSg0FkHuDFcxRJigwCNI3PRImFkkRRVmUJDw18dolkx2h60w0wqD9K09DFpPTYfwmF9Bql+O3hjG9Ep9iu+CREwQTm2u66f36Q+pjaDiHWmr0kgi0zUTD7w0UvlN7gdFCMxk= 15 | - secure: L3nD/BEOAqGTmd8Va9bQ8MZL5gbiNWuy9TvIxCmQ5bfdlAAdq371oqXpuy90JftaloapktjjmuSrsiszWgMMB/TGDO45h1LrYQEMEmA/4JN/uly3lUk8th9Rig+blKTG0q6X6GsX8UWA0xVeLtXzkddpNAOBgpeqb33pWmFkdcM= 16 | - secure: OdVG7LVqQ13RQxxEUPEMHZb0seoZUNzq+oy/K1qe4ubcupqiMh47pxnDcei6vkVpMn8QIvbG9lcV0oOREqJ+m+g3wUA5JX95liFqLNsmLMMVgP3yjYDf7KoNHckA5H5BwIYO/AFCBdwyNN0h439kwSciCbIr70UModVkvdWoFLI= 17 | - secure: RJgKNXCxQZxjkxqinv6x96hGZvfJpjOIaRpH9YUJrCZeqLbS1FUOvTWkze5nZQw4DL14zBmpGZXYC9twSYvscEGNnaEhxDKygiFfLosERRa65N57kphD0AsGNe9YAhWUGDbCxnZ0PYHc48DYxX+jPxqsQ/2bxZbXVoJg4pZFP3o= 18 | - SCREEN_NAME=__twython__ 19 | - PROTECTED_TWITTER_1=TwythonSecure1 20 | - PROTECTED_TWITTER_2=TwythonSecure2 21 | - TEST_TWEET_ID=332992304010899457 22 | - TEST_LIST_SLUG=team 23 | - TEST_LIST_OWNER_SCREEN_NAME=twitterapi 24 | - ACCESS_TOKEN_B64=U2FsdGVkX18QdBhvMNshM4PGy04tU3HLwKP+nNSoNZHKsvGLjELcWEXN2LIu/T+yngX1vGONf9lo14ElnfS4k7sfhiru8phR4+rZuBVP3bDvC2A6fXJuhuLqNhBrWqg32WQewvxLWDWBoKmnvRHg5b74GHh+IN/12tU0cBF2HK8= 25 | install: 26 | - pip install -r requirements.txt 27 | script: nosetests -v -w tests/ --logging-filter="twython" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing 28 | notifications: 29 | email: false 30 | after_success: 31 | - coveralls 32 | before_script: 33 | - export ACCESS_TOKEN=$(echo $ACCESS_TOKEN_B64 | openssl enc -d -aes-256-cbc -A -a -pass env:ACCESS_TOKEN_PASS) 34 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Special Thanks 2 | -------------- 3 | This is a list of all those who have contributed code to Twython in some way, shape, or form. I think it's 4 | exhaustive, but I could be wrong - if you think your name should be here and it's not, please contact 5 | me and let me know (or just issue a pull request on GitHub, and leave a note about it so I can just accept it ;). 6 | 7 | Development Lead 8 | ```````````````` 9 | 10 | - Ryan Mcgrath 11 | 12 | 13 | Patches and Suggestions 14 | ```````````````````````` 15 | 16 | - `Mike Helmick `_, multiple fixes and proper ``requests`` integration, Python 3 compatibility, too much to list here. 17 | - `kracekumar `_, early ``requests`` work and various fixes. 18 | - `Erik Scheffers `_, various fixes regarding OAuth callback URLs. 19 | - `Jordan Bouvier `_, various fixes regarding OAuth callback URLs. 20 | - `Dick Brouwer `_, fixes for OAuth Verifier in ``get_authorized_tokens``. 21 | - `hades `_, Fixes to various initial OAuth issues, raise TwythonError on request error. 22 | - `Alex Sutton `_, fix for parameter substitution regular expression (catch underscores!). 23 | - `Levgen Pyvovarov `_, Various argument fixes, cyrillic text support. 24 | - `Mark Liu `_, Missing parameter fix for ``addListMember``. 25 | - `Randall Degges `_, PEP-8 fixes, MANIFEST.in, installer fixes. 26 | - `Idris Mokhtarzada `_, Fixes for various example code pieces. 27 | - `Jonathan Elsas `_, Fix for original Streaming API stub causing import errors. 28 | - `LuqueDaniel `_, Extended example code where necessary. 29 | - `Mesar Hameed `_, Commit to swap ``__getattr__`` trick for a more debuggable solution. 30 | - `Remy DeCausemaker `_, PEP 8 contributions. 31 | - `mckellister `_ Twitter Spring 2012 Clean Up fixes to ``Exception`` raised by Twython (Rate Limits, etc). 32 | - `Tatz Tsuchiya `_, Fix for ``lambda`` scoping in key injection phase. 33 | - `Mohammed ALDOUB `_, Fixes for ``http/https`` access endpoints. 34 | - `Fumiaki Kinoshita `_, Re-added Proxy support for 2.3.0. 35 | - `Terry Jones `_, Error cleanup and Exception processing in 2.3.0. 36 | - `Leandro Ferreira `_, Fix for double-encoding of search queries in 2.3.0. 37 | - `Chris Brown `_, Updated to use v1.1 endpoints over v1 38 | - `Virendra Rajput `_, Fixed unicode (json) encoding in twython.py 2.7.2. 39 | - `Paul Solbach `_, fixed requirement for oauth_verifier 40 | - `Greg Nofi `_, fixed using built-in Exception attributes for storing & retrieving error message 41 | - `Jonathan Vanasco `_, Debugging support, error_code tracking, Twitter error API tracking, other fixes 42 | - `DevDave `_, quick fix for longs with helper._transparent_params 43 | - `Ruben Varela Rosa `_, Fixed search example 44 | - `Oleg Anashkin `_, streaming ``handlers`` functionality 45 | - `Luis Alberto Santana `_, Fixed issue where Twython was unnecessarily disabling compression 47 | - `Cory Dolphin `_, Added retry_after attribute to TwythonRateLimitError 48 | - `Natan L `_, Fixed typo in documentation 49 | - `Cash Costello `_, Moved tests to use `responsoes`, fixed typos in documentation 50 | - `Joe Cabrera `_, PEP 8 contributions 51 | - `bsbkeven `_, Added `lookup_status` function to `endpoints.py` 52 | - `drevicko `_, Added option to yield full page vs individual results in `cursor` 53 | - `Filipe A Ximenes `_, Added `upload_media` function to `endpoints.py` 54 | - `Mertcan Mermerkaya `_, Fixed code example in documentation 55 | - `Donne Martin `_, Fixed typos in `README.rst` 56 | - `Diego Allen `_, Add missing comma in documentation code snippet 57 | - `Ben McGinnes `_, Added mute API endpoints, a couple of examples, random bits. 58 | - `Davis Silverman `_, Fixed endpoint HTTP method 59 | - `David Beitey `_, Fixed documentation typos 60 | - `7kfpun `_, Fixed documentation typos 61 | - `Will Thompson `_, Fixed documentation typos 62 | - `Andre Polykanine `_, Fixed documentation typos 63 | - `Ben Bertka `_, Added dynamic filtering in streamer 64 | - `Takahashi Shuuji `_, Fixed documentation typos 65 | - `Jeremy Keen `_, Updated documentation example 66 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | ## 3.8.0 (2020-04-02) 4 | - Bump release with latest patches from GitHub. 5 | - Fix Direct Messages with patches from @manuelcortez. 6 | 7 | ## 3.7.0 (2018-07-05) 8 | - Fixes for cursoring API endpoints 9 | - Improve `html_for_tweet()` parsing 10 | - Documentation cleanup 11 | - Documentation for cursor's `return_pages` keyword argument 12 | - Update links to Twitter API in documentation 13 | - Added `create_metadata` endpoint 14 | - Raise error for when cursor is not provided a callable 15 | 16 | ## 3.6.0 (2017-23-08) 17 | - Improve replacing of entities with links in `html_for_tweet()` 18 | - Update classifiers for PyPI 19 | 20 | ## 3.5.0 (2017-06-06) 21 | - Added support for "symbols" in `Twython.html_for_tweet()` 22 | - Added support for extended tweets in `Twython.html_for_tweet()` 23 | - You can now check progress of video uploads to Twitter when using `Twython.upload_video()` 24 | 25 | ## 3.4.0 (2016-30-04) 26 | - Added `upload_video` endpoint 27 | - Fix quoted status checks in `html_for_tweet` 28 | - Fix `html_for_tweet` method response when hashtag/mention is a substring of another 29 | 30 | ## 3.3.0 (2015-18-07) 31 | - Added support for muting users 32 | - Fix typos in documentation 33 | - Updated documentation examples 34 | - Added dynamic filtering to streamer 35 | 36 | ## 3.2.0 (2014-10-30) 37 | - PEP8'd some code 38 | - Added `lookup_status` function to `endpoints.py` 39 | - Added keyword argument to `cursor` to return full pages rather than individual results 40 | - `cursor` now uses while loop rather than recursion 41 | - Fixed issue where Twython was unnecessarily disabling compression 42 | - Using `responses` to mock API calls in tests 43 | - Fixed some typos in documentation 44 | - Added `retry_after` attribute to `TwythonRateLimitError` 45 | - Added `upload_media` method to `Twython` in favor of `update_with_media` 46 | - Deprecating `update_with_media` per Twitter API 1.1 (https://dev.twitter.com/rest/reference/post/statuses/update_with_media) 47 | - Unpin `requests` and `requests-oauthlib` in `requirements.txt` 48 | 49 | ## 3.1.2 (2013-12-05) 50 | - Fixed Changelog (HISTORY.rst) 51 | 52 | ## 3.1.1 (2013-12-05) 53 | - Update `requests` version to 2.1.0. 54 | - 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) 55 | - Fixed issue where XML was returned when bad tokens were passed to `get_authorized_tokens` 56 | - Fixed import for `setup` causing installation to fail on some devices (eg. Nokia N9/MeeGo) 57 | 58 | ## 3.1.0 (2013-09-25) 59 | - 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. 60 | - Pass ``client_args`` to the streaming ``__init__``, much like in core Twython (you can pass headers, timeout, hooks, proxies, etc.). 61 | - 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. 62 | - When an actual request error happens and a ``RequestException`` is raised, it is caught and a ``TwythonError`` is raised instead for convenience. 63 | - Added "cursor"-like functionality. Endpoints with the attribute ``iter_mode`` will be able to be passed to ``Twython.cursor`` and returned as a generator. 64 | - ``Twython.search_gen`` has been deprecated. Please use ``twitter.cursor(twitter.search, q='your_query')`` instead, where ``twitter`` is your ``Twython`` instance. 65 | - Added methods ``get_list_memberships``, ``get_twitter_configuration``, ``get_supported_languages``, ``get_privacy_policy``, ``get_tos`` 66 | - Added ``auth_endpoint`` parameter to ``Twython.__init__`` for cases when the right parameters weren't being shown during the authentication step. 67 | - 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) 68 | - 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') 69 | 70 | ## 3.0.0 (2013-06-18) 71 | - Changed ``twython/twython.py`` to ``twython/api.py`` in attempt to make structure look a little neater 72 | - Removed all camelCase function access (anything like ``getHomeTimeline`` is now ``get_home_timeline``) 73 | - Removed ``shorten_url``. With the ``requests`` library, shortening a URL on your own is simple enough 74 | - ``twitter_token``, ``twitter_secret`` and ``callback_url`` are no longer passed to ``Twython.__init__`` 75 | - ``twitter_token`` and ``twitter_secret`` have been replaced with ``app_key`` and ``app_secret`` respectively 76 | - ``callback_url`` is now passed through ``Twython.get_authentication_tokens`` 77 | - Update ``test_twython.py`` docstrings per http://www.python.org/dev/peps/pep-0257/ 78 | - Removed ``get_list_memberships``, method is Twitter API 1.0 deprecated 79 | - Developers can now pass an array as a parameter to Twitter API methods and they will be automatically joined by a comma and converted to a string 80 | - ``endpoints.py`` now contains ``EndpointsMixin`` (rather than the previous ``api_table`` dict) for Twython, which enables Twython to use functions declared in the Mixin. 81 | - Added OAuth 2 authentication (Application Only) for when you want to make read-only calls to Twitter without having to go through the whole user authentication ritual (see docs for usage) 82 | - Added ``obtain_access_token`` to obtain an OAuth 2 Application Only read-only access token 83 | - ``construct_api_url`` now accepts keyword arguments like other Twython methods (e.g. instead of passing ``{'q': 'twitter', 'result_type': 'recent'}``, pass ``q='twitter', result_type='recent'``) 84 | - Pass ``client_args`` to the Twython ``__init__`` to manipulate request variables. ``client_args`` accepts a dictionary of keywords and values that accepted by ``requests`` (`Session API `_) [ex. headers, proxies, verify(SSL verification)] and the "request" section directly below it. 85 | - Added ``get_application_rate_limit_status`` API method for returning the current rate limits for the specified source 86 | - Added ``invalidate_token`` API method which allows registed apps to revoke an access token presenting its client credentials 87 | - ``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 88 | 89 | ## 2.10.1 (2013-05-29) 90 | - More test coverage! 91 | - Fix ``search_gen`` 92 | - Fixed ``get_lastfunction_header`` to actually do what its docstring says, returns ``None`` if header is not found 93 | - Updated some internal API code, ``__init__`` didn't need to have ``self.auth`` and ``self.headers`` because they were never used anywhere else but the ``__init__`` 94 | - Added ``disconnect`` method to ``TwythonStreamer``, allowing users to disconnect as they desire 95 | - Updated ``TwythonStreamError`` docstring, also allow importing it from ``twython`` 96 | - No longer raise ``TwythonStreamError`` when stream line can't be decoded. Instead, sends signal to ``TwythonStreamer.on_error`` 97 | - Allow for (int, long, float) params to be passed to Twython Twitter API functions in Python 2, and (int, float) in Python 3 98 | 99 | ## 2.10.0 (2013-05-21) 100 | - Added ``get_retweeters_ids`` method 101 | - Fixed ``TwythonDeprecationWarning`` on camelCase functions if the camelCase was the same as the PEP8 function (i.e. ``Twython.retweet`` did not change) 102 | - 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) 103 | - Added "transparent" parameters for making requests, meaning users can pass bool values (True, False) to Twython methods and we convert your params in the background to satisfy the Twitter API. Also, file objects can now be passed seamlessly (see examples in README and in /examples dir for details) 104 | - Callback URL is optional in ``get_authentication_tokens`` to accomedate those using OOB authorization (non web clients) 105 | - Not part of the python package, but tests are now available along with Travis CI hooks 106 | - Added ``__repr__`` definition for Twython, when calling only returning 107 | - Cleaned up ``Twython.construct_api_url``, uses "transparent" parameters (see 4th bullet in this version for explaination) 108 | - Update ``requests`` and ``requests-oauthlib`` requirements, fixing posting files AND post data together, making authenticated requests in general in Python 3.3 109 | 110 | ## 2.9.1 (2013-05-04) 111 | - "PEP8" all the functions. Switch functions from camelCase() to underscore_funcs(). (i.e. ``updateStatus()`` is now ``update_status()``) 112 | 113 | ## 2.9.0 (2013-05-04) 114 | - 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) 115 | - ``Twython`` now requires ``requests-oauthlib`` 0.3.1, fixes #154 (unable to upload media when sending POST data with the file) 116 | 117 | ## 2.8.0 (2013-04-29) 118 | - Added a ``HISTORY.rst`` to start tracking history of changes 119 | - Updated ``twitter_endpoints.py`` to ``endpoints.py`` for cleanliness 120 | - Removed twython3k directory, no longer needed 121 | - Added ``compat.py`` for compatability with Python 2.6 and greater 122 | - Added some ascii art, moved description of Twython and ``__author__`` to ``__init__.py`` 123 | - Added ``version.py`` to store the current Twython version, instead of repeating it twice -- it also had to go into it's own file because of dependencies of ``requests`` and ``requests-oauthlib``, install would fail because those libraries weren't installed yet (on fresh install of Twython) 124 | - Removed ``find_packages()`` from ``setup.py``, only one package (we can just define it) 125 | - added quick publish method for Ryan and I: ``python setup.py publish`` is faster to type and easier to remember than ``python setup.py sdist upload`` 126 | - Removed ``base_url`` from ``endpoints.py`` because we're just repeating it in ``Twython.__init__`` 127 | - ``Twython.get_authentication_tokens()`` now takes ``callback_url`` argument rather than passing the ``callback_url`` through ``Twython.__init__``, ``callback_url`` is only used in the ``get_authentication_tokens`` method and nowhere else (kept in init though for backwards compatability) 128 | - Updated README to better reflect current Twython codebase 129 | - Added ``warnings.simplefilter('default')`` line in ``twython.py`` for Python 2.7 and greater to display Deprecation Warnings in console 130 | - Added Deprecation Warnings for usage of ``twitter_token``, ``twitter_secret`` and ``callback_url`` in ``Twython.__init__`` 131 | - Headers now always include the User-Agent as Twython vXX unless User-Agent is overwritten 132 | - Removed senseless TwythonError thrown if method is not GET or POST, who cares -- if the user passes something other than GET or POST just let Twitter return the error that they messed up 133 | - Removed conversion to unicode of (int, bool) params passed to a requests. ``requests`` isn't greedy about variables that can't be converted to unicode anymore 134 | - Removed `bulkUserLookup` (please use `lookupUser` instead), removed `getProfileImageUrl` (will be completely removed from Twitter API on May 7th, 2013) 135 | - Updated shortenUrl to actually work for those using it, but it is being deprecated since `requests` makes it easy for developers to implement their own url shortening in their app (see https://github.com/ryanmcgrath/twython/issues/184) 136 | - Twython Deprecation Warnings will now be seen in shell when using Python 2.7 and greater 137 | - Twython now takes ``ssl_verify`` parameter, defaults True. Set False if you're having development server issues 138 | - Removed internal ``_media_update`` function, we could have always just used ``self.post`` 139 | 140 | ## 2.7.3 (2013-04-12) 141 | - Fixed issue where Twython Exceptions were not being logged correctly 142 | 143 | ## 2.7.2 (2013-04-08) 144 | - Fixed ``AttributeError`` when trying to decode the JSON response via ``Response.json()`` 145 | 146 | ## 2.7.1 (2013-04-08) 147 | - Removed ``simplejson`` dependency 148 | - Fixed ``destroyDirectMessage``, ``createBlock``, ``destroyBlock`` endpoints in ``twitter_endpoints.py`` 149 | - Added ``getProfileBannerSizes`` method to ``twitter_endpoints.py`` 150 | - Made oauth_verifier argument required in ``get_authorized_tokens`` 151 | - Update ``updateProfileBannerImage`` to use v1.1 endpoint 152 | 153 | ## 2.7.0 (2013-04-04) 154 | - New ``showOwnedLists`` method 155 | 156 | ## 2.7.0 (2013-03-31) 157 | - Added missing slash to ``getMentionsTimeline`` in ``twitter_endpoints.py`` 158 | 159 | ## 2.6.0 (2013-03-29) 160 | - Updated ``twitter_endpoints.py`` to better reflect order of API endpoints on the Twitter API v1.1 docs site 161 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 Ryan McGrath 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md HISTORY.md LICENSE requirements.txt 2 | 3 | recursive-include docs * 4 | prune docs/_build 5 | 6 | recursive-include examples *.py 7 | 8 | recursive-include tests *.py 9 | recursive-include tests/tweets *.json 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twython 2 | 3 | 4 | 5 | 6 | 7 | 8 | `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! 9 | 10 | **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! 11 | 12 | ## Features 13 | - Query data for: 14 | - User information 15 | - Twitter lists 16 | - Timelines 17 | - Direct Messages 18 | - and anything found in [the docs](https://developer.twitter.com/en/docs) 19 | - Image Uploading: 20 | - Update user status with an image 21 | - Change user avatar 22 | - Change user background image 23 | - Change user banner image 24 | - OAuth 2 Application Only (read-only) Support 25 | - Support for Twitter's Streaming API 26 | - Seamless Python 3 support! 27 | 28 | ## Installation 29 | Install Twython via pip: 30 | 31 | ```bash 32 | $ pip install twython 33 | ``` 34 | 35 | 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: 36 | 37 | ``` 38 | pip install twython==3.7.0` 39 | ``` 40 | 41 | Or, if you want the code that is currently on GitHub: 42 | 43 | ```bash 44 | git clone git://github.com/ryanmcgrath/twython.git 45 | cd twython 46 | python setup.py install 47 | ``` 48 | 49 | ## Documentation 50 | Documentation is available at https://twython.readthedocs.io/en/latest/ 51 | 52 | ## Starting Out 53 | First, you'll want to head over to https://apps.twitter.com and register an application! 54 | 55 | After you register, grab your applications `Consumer Key` and `Consumer Secret` from the application details tab. 56 | 57 | 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! 58 | 59 | First, you'll want to import Twython 60 | 61 | ```python 62 | from twython import Twython 63 | ``` 64 | 65 | ## Obtain Authorization URL 66 | Now, you'll want to create a Twython instance with your `Consumer Key` and `Consumer Secret`: 67 | 68 | - Only pass *callback_url* to *get_authentication_tokens* if your application is a Web Application 69 | - Desktop and Mobile Applications **do not** require a callback_url 70 | 71 | ```python 72 | APP_KEY = 'YOUR_APP_KEY' 73 | APP_SECRET = 'YOUR_APP_SECRET' 74 | 75 | twitter = Twython(APP_KEY, APP_SECRET) 76 | 77 | auth = twitter.get_authentication_tokens(callback_url='http://mysite.com/callback') 78 | ``` 79 | 80 | 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 81 | 82 | ```python 83 | OAUTH_TOKEN = auth['oauth_token'] 84 | OAUTH_TOKEN_SECRET = auth['oauth_token_secret'] 85 | ``` 86 | 87 | Send the user to the authentication url, you can obtain it by accessing 88 | 89 | ```python 90 | auth['auth_url'] 91 | ``` 92 | 93 | ## Handling the Callback 94 | If your application is a Desktop or Mobile Application *oauth_verifier* will be the PIN code 95 | 96 | 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`. 97 | 98 | You'll want to extract the `oauth_verifier` from the url. 99 | 100 | Django example: 101 | 102 | ```python 103 | oauth_verifier = request.GET['oauth_verifier'] 104 | ``` 105 | 106 | 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 107 | 108 | ```python 109 | twitter = Twython( 110 | APP_KEY, APP_SECRET, 111 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET 112 | ) 113 | 114 | final_step = twitter.get_authorized_tokens(oauth_verifier) 115 | ``` 116 | 117 | Once you have the final user tokens, store them in a database for later use:: 118 | 119 | ```python 120 | OAUTH_TOKEN = final_step['oauth_token'] 121 | OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] 122 | ``` 123 | 124 | 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). 125 | 126 | ## Dynamic Function Arguments 127 | 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. 128 | 129 | Basic Usage 130 | ----------- 131 | 132 | **Function definitions (i.e. get_home_timeline()) can be found by reading over twython/endpoints.py** 133 | 134 | Create a Twython instance with your application keys and the users OAuth tokens 135 | 136 | ```python 137 | from twython import Twython 138 | twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 139 | ``` 140 | 141 | ## Authenticated Users Home Timeline 142 | ```python 143 | twitter.get_home_timeline() 144 | ``` 145 | 146 | ## Updating Status 147 | This method makes use of dynamic arguments, [read more about them](https://twython.readthedocs.io/en/latest/usage/starting_out.html#dynamic-function-arguments). 148 | 149 | ```python 150 | twitter.update_status(status='See how easy using Twython is!') 151 | ``` 152 | 153 | ## Advanced Usage 154 | - [Advanced Twython Usage](https://twython.readthedocs.io/en/latest/usage/advanced_usage.html) 155 | - [Streaming with Twython](https://twython.readthedocs.io/en/latest/usage/streaming_api.html) 156 | 157 | 158 | ## Questions, Comments, etc? 159 | 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. 160 | 161 | Or if I'm to busy to answer, feel free to ping mikeh@ydekproductions.com as well. 162 | 163 | Follow us on Twitter: 164 | 165 | - [@ryanmcgrath](https://twitter.com/ryanmcgrath) 166 | - [@mikehelmick](https://twitter.com/mikehelmick) 167 | 168 | ## Want to help? 169 | 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! 170 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Twython.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Twython.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Twython" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Twython" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | .section { 2 | margin-bottom: 30px !important; 3 | } 4 | 5 | .tip { 6 | color: #3a87ad; /* Basicstrap CSS was messed up so override it */ 7 | } 8 | 9 | @media (min-width: 1200px) { 10 | .collapse .pull-right{ 11 | margin-right: 115px !important; 12 | } 13 | } 14 | 15 | .literal { 16 | background: #f5f5f5; 17 | padding: 2px 3px 3px; 18 | border: 1px solid #ccc; 19 | color: #11729B; 20 | -webkit-border-radius: 3px; 21 | border-radius: 3px; 22 | margin: 0 3px; 23 | } 24 | -------------------------------------------------------------------------------- /docs/_themes/basicstrap/customsidebar.html: -------------------------------------------------------------------------------- 1 |

{{ _('Links') }}

2 | 6 | -------------------------------------------------------------------------------- /docs/_themes/basicstrap/layout.html: -------------------------------------------------------------------------------- 1 | {# 2 | basicstrap/layout.html 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Master layout template for Sphinx themes. on Twitter Bootstrap 6 | 7 | :copyright: Copyright 2012 by tell-k. 8 | :license: MIT Licence, see LICENSE for details. 9 | #} 10 | {%- block doctype -%} 11 | 12 | {%- endblock %} 13 | {%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} 14 | {%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} 15 | {%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and 16 | (sidebars != []) %} 17 | {%- set url_root = pathto('', 1) %} 18 | {# XXX necessary? #} 19 | {%- if url_root == '#' %}{% set url_root = '' %}{% endif %} 20 | {%- if not embedded and docstitle %} 21 | {%- set titlesuffix = " — "|safe + docstitle|e %} 22 | {%- else %} 23 | {%- set titlesuffix = "" %} 24 | {%- endif %} 25 | 26 | Fork me on GitHub 27 | 28 | {%- macro relbar() %} 29 | 55 | {%- endmacro %} 56 | 57 | {%- macro sidebar(for_mobile=False) %} 58 | {%- if render_sidebar %} 59 |
60 | {%- if (for_mobile|tobool) and (not theme_noresponsive|tobool) %} 61 | Open Table Of Contents 62 | {%- endif %} 63 | 90 |
91 | {%- endif %} 92 | {%- endmacro %} 93 | 94 | {% set script_files = script_files + ['_static/js/bootstrap.min.js'] %} 95 | {%- macro script() %} 96 | 105 | {%- for scriptfile in script_files %} 106 | {%- if scriptfile == '_static/jquery.js' %} 107 | 108 | {%- else %} 109 | 110 | {%- endif %} 111 | {%- endfor %} 112 | 125 | {%- endmacro %} 126 | 127 | {%- macro css() %} 128 | {%- if (theme_googlewebfont|tobool) %} 129 | 130 | {%- endif %} 131 | 132 | {%- if (theme_inner_theme|tobool) %} 133 | 134 | {%- else %} 135 | 136 | {%- endif %} 137 | 138 | 141 | 148 | 149 | 150 | 151 | {%- for cssfile in css_files %} 152 | 153 | {%- endfor %} 154 | {%- if (not theme_noresponsive|tobool) %} 155 | 156 | {%- endif %} 157 | {%- endmacro %} 158 | 159 | 160 | 161 | 162 | {{ metatags }} 163 | {%- block htmltitle %} 164 | {{ title|striptags|e }}{{ titlesuffix }} 165 | {%- endblock %} 166 | 167 | 168 | 169 | 170 | 171 | {{ css() }} 172 | {%- if not embedded %} 173 | {{ script() }} 174 | {%- if use_opensearch %} 175 | 178 | {%- endif %} 179 | {%- if favicon %} 180 | 181 | {%- endif %} 182 | {%- endif %} 183 | {%- block linktags %} 184 | {%- if hasdoc('about') %} 185 | 186 | {%- endif %} 187 | {%- if hasdoc('genindex') %} 188 | 189 | {%- endif %} 190 | {%- if hasdoc('search') %} 191 | 192 | {%- endif %} 193 | {%- if hasdoc('copyright') %} 194 | 195 | {%- endif %} 196 | 197 | {%- if parents %} 198 | 199 | {%- endif %} 200 | {%- if next %} 201 | 202 | {%- endif %} 203 | {%- if prev %} 204 | 205 | {%- endif %} 206 | {%- endblock %} 207 | {%- block extrahead %} {% endblock %} 208 | 209 | 210 | 211 | {%- block header %} 212 | 229 | {% endblock %} 230 | 231 | 232 |
233 | 234 | 235 | {% if not theme_noresponsive|tobool %} 236 |
237 | {{ sidebar(for_mobile=True) }} 238 |
239 | {% endif %} 240 | 241 | 242 |
243 | {%- block content %} 244 | 245 | {%- block sidebar1 %} 246 | {% if (not theme_rightsidebar|tobool) %} {{ sidebar() }} {% endif %} 247 | {% endblock %} 248 | 249 |
250 |
251 | {%- block document %} 252 |
253 | {%- if render_sidebar %} 254 |
255 | {%- endif %} 256 |
257 | {% block body %} {% endblock %} 258 |
259 | {%- if render_sidebar %} 260 |
261 | {%- endif %} 262 |
263 | {%- endblock %} 264 |
265 |
266 | 267 | {%- block sidebar2 %} 268 | {% if (theme_rightsidebar|tobool) %} {{ sidebar() }} {% endif %} 269 | {% endblock %} 270 | 271 | {%- endblock %}{# /content block #} 272 |
273 | 274 | 275 |
276 | {%- block relbar2 %}{{ relbar() }}{% endblock %} 277 |
278 | 279 | 280 | {%- block footer %} 281 |
282 | {%- if show_copyright %} 283 | {%- if hasdoc('copyright') %} 284 | {% trans path=pathto('copyright'), copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %} 285 | {%- else %} 286 | {% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %} 287 | {%- endif %} 288 | {%- endif %} 289 | {%- if last_updated %} 290 | {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %} 291 | {%- endif %} 292 | {%- if show_sphinx %} 293 | {% trans sphinx_version=sphinx_version|e %}Created using Sphinx {{ sphinx_version }}.{% endtrans %} 294 | {%- endif %} 295 |
296 | {%- endblock %} 297 | 298 | 299 |
300 | 301 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /docs/_themes/basicstrap/search.html: -------------------------------------------------------------------------------- 1 | {# 2 | basicstrap/search.html 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Template for the search page. 6 | 7 | :copyright: Copyright 2012 by tell-k. 8 | :license: MIT Licence, see LICENSE for details. 9 | #} 10 | {% extends "layout.html" %} 11 | {% set title = _('Search') %} 12 | {% set script_files = script_files + ['_static/searchtools.js'] %} 13 | {% block extrahead %} 14 | 17 | {{ super() }} 18 | {% endblock %} 19 | {% block body %} 20 |

{{ _('Search') }}

21 |
22 | 23 |

24 | {% trans %}Please activate JavaScript to enable the search 25 | functionality.{% endtrans %} 26 |

27 |
28 |

29 | {% trans %}From here you can search these documents. Enter your search 30 | words into the box below and click "search". Note that the search 31 | function will automatically search for all of the words. Pages 32 | containing fewer words won't appear in the result list.{% endtrans %} 33 |

34 | 41 | {% if search_performed %} 42 |

{{ _('Search Results') }}

43 | {% if not search_results %} 44 |

{{ _('Your search did not match any results.') }}

45 | {% endif %} 46 | {% endif %} 47 |
48 | {% if search_results %} 49 |
    50 | {% for href, caption, context in search_results %} 51 |
  • {{ caption }} 52 |
    {{ context|e }}
    53 |
  • 54 | {% endfor %} 55 |
56 | {% endif %} 57 |
58 | {% endblock %} 59 | -------------------------------------------------------------------------------- /docs/_themes/basicstrap/searchbox.html: -------------------------------------------------------------------------------- 1 | {# 2 | basicstrap/searchbox.html 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Sphinx sidebar template: quick search box. 6 | 7 | :copyright: Copyright 2012 by tell-k. 8 | :license: MIT Licence, see LICENSE for details. 9 | #} 10 | {%- if pagename != "search" %} 11 | 25 | {%- endif %} 26 | -------------------------------------------------------------------------------- /docs/_themes/basicstrap/searchresults.html: -------------------------------------------------------------------------------- 1 | {# 2 | basicstrap/searchresults.html 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Template for the body of the search results page. 6 | 7 | :copyright: Copyright 2012 by tell-k. 8 | :license: MIT Licence, see LICENSE for details. 9 | #} 10 |

Search

11 |

12 | From here you can search these documents. Enter your search 13 | words into the box below and click "search". 14 |

15 | 22 | {%- if search_performed %} 23 |

Search Results

24 | {%- if not search_results %} 25 |

Your search did not match any results.

26 | {%- endif %} 27 | {%- endif %} 28 |
29 | {%- if search_results %} 30 | 37 | {%- endif %} 38 |
39 | -------------------------------------------------------------------------------- /docs/_themes/basicstrap/static/css/basicstrap-base.css: -------------------------------------------------------------------------------- 1 | @import url("../basic.css"); 2 | 3 | /* Table 4 | ------------------------------------------------------- */ 5 | table.docutils { 6 | border: none; 7 | border-collapse: none; 8 | } 9 | 10 | table.docutils th, 11 | table.docutils td 12 | { 13 | border: none; 14 | border-collapse: none; 15 | } 16 | 17 | table { 18 | max-width: 100%; 19 | background-color: transparent; 20 | border-collapse: collapse; 21 | border-spacing: 0 !important; 22 | } 23 | 24 | table.docutils { 25 | width: 100%; 26 | margin-bottom: 20px; 27 | } 28 | 29 | table.docutils th, 30 | table.docutils td { 31 | padding: 8px; 32 | line-height: 20px; 33 | text-align: left; 34 | vertical-align: top; 35 | border-spacing: 0 !important; 36 | border-top: 1px solid #dddddd; 37 | } 38 | 39 | table.docutils th { 40 | font-weight: bold; 41 | } 42 | 43 | table.docutils thead th { 44 | vertical-align: bottom; 45 | } 46 | 47 | table.docutils caption + thead tr:first-child th, 48 | table.docutils caption + thead tr:first-child td, 49 | table.docutils colgroup + thead tr:first-child th, 50 | table.docutils colgroup + thead tr:first-child td, 51 | table.docutils thead:first-child tr:first-child th, 52 | table.docutils thead:first-child tr:first-child td { 53 | border-top: 0; 54 | } 55 | 56 | table.docutils tbody + tbody { 57 | border-top: 2px solid #dddddd; 58 | } 59 | 60 | table.docutils th, 61 | table.docutils td { 62 | padding: 4px 5px; 63 | } 64 | 65 | table.docutils { 66 | border: 1px solid #dddddd; 67 | border-collapse: separate; 68 | *border-collapse: collapse; 69 | border-left: 0; 70 | -webkit-border-radius: 4px; 71 | -moz-border-radius: 4px; 72 | border-radius: 4px; 73 | } 74 | 75 | table.docutils th, 76 | table.docutils td { 77 | border-left: 1px solid #dddddd; 78 | } 79 | 80 | table.docutils caption + thead tr:first-child th, 81 | table.docutils caption + tbody tr:first-child th, 82 | table.docutils caption + tbody tr:first-child td, 83 | table.docutils colgroup + thead tr:first-child th, 84 | table.docutils colgroup + tbody tr:first-child th, 85 | table.docutils colgroup + tbody tr:first-child td, 86 | table.docutils thead:first-child tr:first-child th, 87 | table.docutils tbody:first-child tr:first-child th, 88 | table.docutils tbody:first-child tr:first-child td { 89 | border-top: 0; 90 | } 91 | 92 | table.docutils thead:first-child tr:first-child th:first-child, 93 | table.docutils tbody:first-child tr:first-child td:first-child { 94 | -webkit-border-top-left-radius: 4px; 95 | border-top-left-radius: 4px; 96 | -moz-border-radius-topleft: 4px; 97 | } 98 | 99 | table.docutils thead:first-child tr:first-child th:last-child, 100 | table.docutils tbody:first-child tr:first-child td:last-child { 101 | -webkit-border-top-right-radius: 4px; 102 | border-top-right-radius: 4px; 103 | -moz-border-radius-topright: 4px; 104 | } 105 | 106 | table.docutils thead:last-child tr:last-child th:first-child, 107 | table.docutils tbody:last-child tr:last-child td:first-child, 108 | table.docutils tfoot:last-child tr:last-child td:first-child { 109 | -webkit-border-radius: 0 0 0 4px; 110 | -moz-border-radius: 0 0 0 4px; 111 | border-radius: 0 0 0 4px; 112 | -webkit-border-bottom-left-radius: 4px; 113 | border-bottom-left-radius: 4px; 114 | -moz-border-radius-bottomleft: 4px; 115 | } 116 | 117 | table.docutils thead:last-child tr:last-child th:last-child, 118 | table.docutils tbody:last-child tr:last-child td:last-child, 119 | table.docutils tfoot:last-child tr:last-child td:last-child { 120 | -webkit-border-bottom-right-radius: 4px; 121 | border-bottom-right-radius: 4px; 122 | -moz-border-radius-bottomright: 4px; 123 | } 124 | 125 | table.docutils caption + thead tr:first-child th:first-child, 126 | table.docutils caption + tbody tr:first-child td:first-child, 127 | table.docutils colgroup + thead tr:first-child th:first-child, 128 | table.docutils colgroup + tbody tr:first-child td:first-child { 129 | -webkit-border-top-left-radius: 4px; 130 | border-top-left-radius: 4px; 131 | -moz-border-radius-topleft: 4px; 132 | } 133 | 134 | table.docutils caption + thead tr:first-child th:last-child, 135 | table.docutils caption + tbody tr:first-child td:last-child, 136 | table.docutils colgroup + thead tr:first-child th:last-child, 137 | table.docutils colgroup + tbody tr:first-child td:last-child { 138 | -webkit-border-top-right-radius: 4px; 139 | border-top-right-radius: 4px; 140 | -moz-border-radius-topleft: 4px; 141 | } 142 | 143 | table.docutils tbody tr:nth-child(odd) td, 144 | table.docutils tbody tr:nth-child(odd) th { 145 | background-color: #f9f9f9; 146 | } 147 | 148 | table.docutils tbody tr:hover td, 149 | table.docutils tbody tr:hover th { 150 | background-color: #f5f5f5; 151 | } 152 | 153 | /* Sidebar 154 | ------------------------------------------------------- */ 155 | div.sidebar { 156 | border: none; 157 | margin: 0; 158 | padding: 0; 159 | background: none; 160 | width: 100%; 161 | float: none; 162 | } 163 | 164 | /* Admonition styles 165 | ----------------------------------------------------------- */ 166 | div.admonition { 167 | padding: 8px 35px 8px 0px; 168 | margin-bottom: 20px; 169 | color: #c09853; 170 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 171 | background-color: #fcf8e3; 172 | border: 1px solid #fbeed5; 173 | -webkit-border-radius: 4px; 174 | -moz-border-radius: 4px; 175 | border-radius: 4px; 176 | } 177 | 178 | div.admonition p { 179 | margin: 0.5em 1em 0.5em 1em; 180 | padding: 0; 181 | } 182 | 183 | div.admonition pre { 184 | margin: 0.4em 1em 0.4em 1em; 185 | } 186 | 187 | div.admonition p.admonition-title { 188 | margin: 0; 189 | padding: 0.1em 0 0.1em 0.5em; 190 | font-weight: bold; 191 | } 192 | 193 | div.admonition ul, div.admonition ol { 194 | margin: 0.1em 0.5em 0.5em 3em; 195 | padding: 0; 196 | } 197 | 198 | /* -- danger, error -- */ 199 | div.danger, 200 | div.error { 201 | color: #b94a48; 202 | background-color: #f2dede; 203 | border-color: #eed3d7; 204 | } 205 | 206 | 207 | /* -- warning, caution, attention -- */ 208 | div.warning, 209 | div.caution, 210 | div.attention { 211 | 212 | } 213 | 214 | /* -- note, important -- */ 215 | div.note, 216 | div.important { 217 | color: #468847; 218 | background-color: #dff0d8; 219 | border-color: #d6e9c6; 220 | } 221 | 222 | /* -- hint, tip -- */ 223 | div.hint, 224 | div.tip { 225 | color: #3a87ad; 226 | background-color: #d9edf7; 227 | border-color: #bce8f1; 228 | } 229 | 230 | /* Etc 231 | ----------------------------------------------------------- */ 232 | 233 | div.figure-round img { 234 | -webkit-border-radius: 6px; 235 | -moz-border-radius: 6px; 236 | border-radius: 6px; 237 | } 238 | 239 | div.figure-polaroid img{ 240 | padding: 4px; 241 | background-color: #fff; 242 | border: 1px solid #ccc; 243 | border: 1px solid rgba(0, 0, 0, 0.2); 244 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 245 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 246 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 247 | } 248 | 249 | div.figure-circle img { 250 | -webkit-border-radius: 500px; 251 | -moz-border-radius: 500px; 252 | border-radius: 500px; 253 | } 254 | 255 | a.headerlink { 256 | font-size: 60% !important; 257 | padding-left: 5px; 258 | } 259 | 260 | h2, h3, h4, h5, h6 { 261 | border-bottom: 1px solid #d0d0d0; 262 | } 263 | 264 | #searchbox .search-query { 265 | width: 60%; 266 | } 267 | 268 | div.topic { 269 | min-height: 20px; 270 | padding: 9px; 271 | margin-bottom: 20px; 272 | 273 | background-color: #f5f5f5; 274 | border: 1px solid #e3e3e3; 275 | 276 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); 277 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); 278 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); 279 | -webkit-border-radius: 3px; 280 | -moz-border-radius: 3px; 281 | border-radius: 3px; 282 | } 283 | 284 | -------------------------------------------------------------------------------- /docs/_themes/basicstrap/static/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.2.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */@-ms-viewport{width:device-width}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /docs/_themes/basicstrap/static/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 3.0.2 3 | * the iconic font designed for use with Twitter Bootstrap 4 | * ------------------------------------------------------- 5 | * The full suite of pictographic icons, examples, and documentation 6 | * can be found at: http://fortawesome.github.com/Font-Awesome/ 7 | * 8 | * License 9 | * ------------------------------------------------------- 10 | * - The Font Awesome font is licensed under the SIL Open Font License - http://scripts.sil.org/OFL 11 | * - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License - 12 | * http://opensource.org/licenses/mit-license.html 13 | * - The Font Awesome pictograms are licensed under the CC BY 3.0 License - http://creativecommons.org/licenses/by/3.0/ 14 | * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: 15 | * "Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome" 16 | 17 | * Contact 18 | * ------------------------------------------------------- 19 | * Email: dave@davegandy.com 20 | * Twitter: http://twitter.com/fortaweso_me 21 | * Work: Lead Product Designer @ http://kyruus.com 22 | */ 23 | 24 | @font-face{ 25 | font-family:'FontAwesome'; 26 | src:url('../font/fontawesome-webfont.eot?v=3.0.1'); 27 | src:url('../font/fontawesome-webfont.eot?#iefix&v=3.0.1') format('embedded-opentype'), 28 | url('../font/fontawesome-webfont.woff?v=3.0.1') format('woff'), 29 | url('../font/fontawesome-webfont.ttf?v=3.0.1') format('truetype'); 30 | font-weight:normal; 31 | font-style:normal } 32 | 33 | [class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0 0;background-repeat:repeat;margin-top:0}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none}[class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none}a [class^="icon-"],a [class*=" icon-"]{display:inline-block}.icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em}.btn [class^="icon-"],.nav [class^="icon-"],.btn [class*=" icon-"],.nav [class*=" icon-"]{display:inline}.btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em}.btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block}.nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em}li [class^="icon-"],.nav li [class^="icon-"],li [class*=" icon-"],.nav li [class*=" icon-"]{display:inline-block;width:1.25em;text-align:center}li [class^="icon-"].icon-large,.nav li [class^="icon-"].icon-large,li [class*=" icon-"].icon-large,.nav li [class*=" icon-"].icon-large{width:1.5625em}ul.icons{list-style-type:none;text-indent:-0.75em}ul.icons li [class^="icon-"],ul.icons li [class*=" icon-"]{width:.75em}.icon-muted{color:#eee}.icon-border{border:solid 1px #eee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.icon-2x{font-size:2em}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.icon-3x{font-size:3em}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.icon-4x{font-size:4em}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.pull-right{float:right}.pull-left{float:left}[class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em}[class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em}.btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em}.btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em}.btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em}.btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em}.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em}.icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}@-moz-document url-prefix(){.icon-spin{height:.9em}.btn .icon-spin{height:auto}.icon-spin.icon-large{height:1.25em}.btn .icon-spin.icon-large{height:.75em}}.icon-glass:before{content:"\f000"}.icon-music:before{content:"\f001"}.icon-search:before{content:"\f002"}.icon-envelope:before{content:"\f003"}.icon-heart:before{content:"\f004"}.icon-star:before{content:"\f005"}.icon-star-empty:before{content:"\f006"}.icon-user:before{content:"\f007"}.icon-film:before{content:"\f008"}.icon-th-large:before{content:"\f009"}.icon-th:before{content:"\f00a"}.icon-th-list:before{content:"\f00b"}.icon-ok:before{content:"\f00c"}.icon-remove:before{content:"\f00d"}.icon-zoom-in:before{content:"\f00e"}.icon-zoom-out:before{content:"\f010"}.icon-off:before{content:"\f011"}.icon-signal:before{content:"\f012"}.icon-cog:before{content:"\f013"}.icon-trash:before{content:"\f014"}.icon-home:before{content:"\f015"}.icon-file:before{content:"\f016"}.icon-time:before{content:"\f017"}.icon-road:before{content:"\f018"}.icon-download-alt:before{content:"\f019"}.icon-download:before{content:"\f01a"}.icon-upload:before{content:"\f01b"}.icon-inbox:before{content:"\f01c"}.icon-play-circle:before{content:"\f01d"}.icon-repeat:before{content:"\f01e"}.icon-refresh:before{content:"\f021"}.icon-list-alt:before{content:"\f022"}.icon-lock:before{content:"\f023"}.icon-flag:before{content:"\f024"}.icon-headphones:before{content:"\f025"}.icon-volume-off:before{content:"\f026"}.icon-volume-down:before{content:"\f027"}.icon-volume-up:before{content:"\f028"}.icon-qrcode:before{content:"\f029"}.icon-barcode:before{content:"\f02a"}.icon-tag:before{content:"\f02b"}.icon-tags:before{content:"\f02c"}.icon-book:before{content:"\f02d"}.icon-bookmark:before{content:"\f02e"}.icon-print:before{content:"\f02f"}.icon-camera:before{content:"\f030"}.icon-font:before{content:"\f031"}.icon-bold:before{content:"\f032"}.icon-italic:before{content:"\f033"}.icon-text-height:before{content:"\f034"}.icon-text-width:before{content:"\f035"}.icon-align-left:before{content:"\f036"}.icon-align-center:before{content:"\f037"}.icon-align-right:before{content:"\f038"}.icon-align-justify:before{content:"\f039"}.icon-list:before{content:"\f03a"}.icon-indent-left:before{content:"\f03b"}.icon-indent-right:before{content:"\f03c"}.icon-facetime-video:before{content:"\f03d"}.icon-picture:before{content:"\f03e"}.icon-pencil:before{content:"\f040"}.icon-map-marker:before{content:"\f041"}.icon-adjust:before{content:"\f042"}.icon-tint:before{content:"\f043"}.icon-edit:before{content:"\f044"}.icon-share:before{content:"\f045"}.icon-check:before{content:"\f046"}.icon-move:before{content:"\f047"}.icon-step-backward:before{content:"\f048"}.icon-fast-backward:before{content:"\f049"}.icon-backward:before{content:"\f04a"}.icon-play:before{content:"\f04b"}.icon-pause:before{content:"\f04c"}.icon-stop:before{content:"\f04d"}.icon-forward:before{content:"\f04e"}.icon-fast-forward:before{content:"\f050"}.icon-step-forward:before{content:"\f051"}.icon-eject:before{content:"\f052"}.icon-chevron-left:before{content:"\f053"}.icon-chevron-right:before{content:"\f054"}.icon-plus-sign:before{content:"\f055"}.icon-minus-sign:before{content:"\f056"}.icon-remove-sign:before{content:"\f057"}.icon-ok-sign:before{content:"\f058"}.icon-question-sign:before{content:"\f059"}.icon-info-sign:before{content:"\f05a"}.icon-screenshot:before{content:"\f05b"}.icon-remove-circle:before{content:"\f05c"}.icon-ok-circle:before{content:"\f05d"}.icon-ban-circle:before{content:"\f05e"}.icon-arrow-left:before{content:"\f060"}.icon-arrow-right:before{content:"\f061"}.icon-arrow-up:before{content:"\f062"}.icon-arrow-down:before{content:"\f063"}.icon-share-alt:before{content:"\f064"}.icon-resize-full:before{content:"\f065"}.icon-resize-small:before{content:"\f066"}.icon-plus:before{content:"\f067"}.icon-minus:before{content:"\f068"}.icon-asterisk:before{content:"\f069"}.icon-exclamation-sign:before{content:"\f06a"}.icon-gift:before{content:"\f06b"}.icon-leaf:before{content:"\f06c"}.icon-fire:before{content:"\f06d"}.icon-eye-open:before{content:"\f06e"}.icon-eye-close:before{content:"\f070"}.icon-warning-sign:before{content:"\f071"}.icon-plane:before{content:"\f072"}.icon-calendar:before{content:"\f073"}.icon-random:before{content:"\f074"}.icon-comment:before{content:"\f075"}.icon-magnet:before{content:"\f076"}.icon-chevron-up:before{content:"\f077"}.icon-chevron-down:before{content:"\f078"}.icon-retweet:before{content:"\f079"}.icon-shopping-cart:before{content:"\f07a"}.icon-folder-close:before{content:"\f07b"}.icon-folder-open:before{content:"\f07c"}.icon-resize-vertical:before{content:"\f07d"}.icon-resize-horizontal:before{content:"\f07e"}.icon-bar-chart:before{content:"\f080"}.icon-twitter-sign:before{content:"\f081"}.icon-facebook-sign:before{content:"\f082"}.icon-camera-retro:before{content:"\f083"}.icon-key:before{content:"\f084"}.icon-cogs:before{content:"\f085"}.icon-comments:before{content:"\f086"}.icon-thumbs-up:before{content:"\f087"}.icon-thumbs-down:before{content:"\f088"}.icon-star-half:before{content:"\f089"}.icon-heart-empty:before{content:"\f08a"}.icon-signout:before{content:"\f08b"}.icon-linkedin-sign:before{content:"\f08c"}.icon-pushpin:before{content:"\f08d"}.icon-external-link:before{content:"\f08e"}.icon-signin:before{content:"\f090"}.icon-trophy:before{content:"\f091"}.icon-github-sign:before{content:"\f092"}.icon-upload-alt:before{content:"\f093"}.icon-lemon:before{content:"\f094"}.icon-phone:before{content:"\f095"}.icon-check-empty:before{content:"\f096"}.icon-bookmark-empty:before{content:"\f097"}.icon-phone-sign:before{content:"\f098"}.icon-twitter:before{content:"\f099"}.icon-facebook:before{content:"\f09a"}.icon-github:before{content:"\f09b"}.icon-unlock:before{content:"\f09c"}.icon-credit-card:before{content:"\f09d"}.icon-rss:before{content:"\f09e"}.icon-hdd:before{content:"\f0a0"}.icon-bullhorn:before{content:"\f0a1"}.icon-bell:before{content:"\f0a2"}.icon-certificate:before{content:"\f0a3"}.icon-hand-right:before{content:"\f0a4"}.icon-hand-left:before{content:"\f0a5"}.icon-hand-up:before{content:"\f0a6"}.icon-hand-down:before{content:"\f0a7"}.icon-circle-arrow-left:before{content:"\f0a8"}.icon-circle-arrow-right:before{content:"\f0a9"}.icon-circle-arrow-up:before{content:"\f0aa"}.icon-circle-arrow-down:before{content:"\f0ab"}.icon-globe:before{content:"\f0ac"}.icon-wrench:before{content:"\f0ad"}.icon-tasks:before{content:"\f0ae"}.icon-filter:before{content:"\f0b0"}.icon-briefcase:before{content:"\f0b1"}.icon-fullscreen:before{content:"\f0b2"}.icon-group:before{content:"\f0c0"}.icon-link:before{content:"\f0c1"}.icon-cloud:before{content:"\f0c2"}.icon-beaker:before{content:"\f0c3"}.icon-cut:before{content:"\f0c4"}.icon-copy:before{content:"\f0c5"}.icon-paper-clip:before{content:"\f0c6"}.icon-save:before{content:"\f0c7"}.icon-sign-blank:before{content:"\f0c8"}.icon-reorder:before{content:"\f0c9"}.icon-list-ul:before{content:"\f0ca"}.icon-list-ol:before{content:"\f0cb"}.icon-strikethrough:before{content:"\f0cc"}.icon-underline:before{content:"\f0cd"}.icon-table:before{content:"\f0ce"}.icon-magic:before{content:"\f0d0"}.icon-truck:before{content:"\f0d1"}.icon-pinterest:before{content:"\f0d2"}.icon-pinterest-sign:before{content:"\f0d3"}.icon-google-plus-sign:before{content:"\f0d4"}.icon-google-plus:before{content:"\f0d5"}.icon-money:before{content:"\f0d6"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.icon-columns:before{content:"\f0db"}.icon-sort:before{content:"\f0dc"}.icon-sort-down:before{content:"\f0dd"}.icon-sort-up:before{content:"\f0de"}.icon-envelope-alt:before{content:"\f0e0"}.icon-linkedin:before{content:"\f0e1"}.icon-undo:before{content:"\f0e2"}.icon-legal:before{content:"\f0e3"}.icon-dashboard:before{content:"\f0e4"}.icon-comment-alt:before{content:"\f0e5"}.icon-comments-alt:before{content:"\f0e6"}.icon-bolt:before{content:"\f0e7"}.icon-sitemap:before{content:"\f0e8"}.icon-umbrella:before{content:"\f0e9"}.icon-paste:before{content:"\f0ea"}.icon-lightbulb:before{content:"\f0eb"}.icon-exchange:before{content:"\f0ec"}.icon-cloud-download:before{content:"\f0ed"}.icon-cloud-upload:before{content:"\f0ee"}.icon-user-md:before{content:"\f0f0"}.icon-stethoscope:before{content:"\f0f1"}.icon-suitcase:before{content:"\f0f2"}.icon-bell-alt:before{content:"\f0f3"}.icon-coffee:before{content:"\f0f4"}.icon-food:before{content:"\f0f5"}.icon-file-alt:before{content:"\f0f6"}.icon-building:before{content:"\f0f7"}.icon-hospital:before{content:"\f0f8"}.icon-ambulance:before{content:"\f0f9"}.icon-medkit:before{content:"\f0fa"}.icon-fighter-jet:before{content:"\f0fb"}.icon-beer:before{content:"\f0fc"}.icon-h-sign:before{content:"\f0fd"}.icon-plus-sign-alt:before{content:"\f0fe"}.icon-double-angle-left:before{content:"\f100"}.icon-double-angle-right:before{content:"\f101"}.icon-double-angle-up:before{content:"\f102"}.icon-double-angle-down:before{content:"\f103"}.icon-angle-left:before{content:"\f104"}.icon-angle-right:before{content:"\f105"}.icon-angle-up:before{content:"\f106"}.icon-angle-down:before{content:"\f107"}.icon-desktop:before{content:"\f108"}.icon-laptop:before{content:"\f109"}.icon-tablet:before{content:"\f10a"}.icon-mobile-phone:before{content:"\f10b"}.icon-circle-blank:before{content:"\f10c"}.icon-quote-left:before{content:"\f10d"}.icon-quote-right:before{content:"\f10e"}.icon-spinner:before{content:"\f110"}.icon-circle:before{content:"\f111"}.icon-reply:before{content:"\f112"}.icon-github-alt:before{content:"\f113"}.icon-folder-close-alt:before{content:"\f114"}.icon-folder-open-alt:before{content:"\f115"} -------------------------------------------------------------------------------- /docs/_themes/basicstrap/static/font/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanmcgrath/twython/0c405604285364457f3c309969f11ba68163bd05/docs/_themes/basicstrap/static/font/FontAwesome.otf -------------------------------------------------------------------------------- /docs/_themes/basicstrap/static/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanmcgrath/twython/0c405604285364457f3c309969f11ba68163bd05/docs/_themes/basicstrap/static/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/_themes/basicstrap/static/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanmcgrath/twython/0c405604285364457f3c309969f11ba68163bd05/docs/_themes/basicstrap/static/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/_themes/basicstrap/static/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanmcgrath/twython/0c405604285364457f3c309969f11ba68163bd05/docs/_themes/basicstrap/static/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/_themes/basicstrap/static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanmcgrath/twython/0c405604285364457f3c309969f11ba68163bd05/docs/_themes/basicstrap/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /docs/_themes/basicstrap/static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanmcgrath/twython/0c405604285364457f3c309969f11ba68163bd05/docs/_themes/basicstrap/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /docs/_themes/basicstrap/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = basicstrap.css 4 | pygments_style = friendly 5 | 6 | [options] 7 | lang = en 8 | nosidebar = false 9 | rightsidebar = false 10 | sidebar_span = 3 11 | 12 | nav_fixed = false 13 | content_fixed = false 14 | row_fixed = false 15 | noresponsive = false 16 | 17 | nav_width = 900px 18 | content_width = 900px 19 | 20 | googlewebfont = false 21 | googlewebfont_url = http://fonts.googleapis.com/css?family=Text+Me+One 22 | googlewebfont_style = font-family: 'Text Me One', sans-serif; 23 | 24 | header_inverse = false 25 | relbar_inverse = false 26 | 27 | inner_theme = false 28 | inner_theme_name = bootswatch-amelia 29 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | Developer Interface 4 | =================== 5 | 6 | .. module:: twython 7 | 8 | This page of the documentation will cover all methods and classes available to the developer. 9 | 10 | Twython, currently, has two main interfaces: 11 | 12 | - Twitter's Core API (updating statuses, getting timelines, direct messaging, etc) 13 | - Twitter's Streaming API 14 | 15 | Core Interface 16 | -------------- 17 | 18 | .. autoclass:: Twython 19 | :special-members: __init__ 20 | :inherited-members: 21 | 22 | .. _streaming_interface: 23 | 24 | Streaming Interface 25 | ------------------- 26 | 27 | .. autoclass:: TwythonStreamer 28 | :special-members: __init__ 29 | :inherited-members: 30 | 31 | Streaming Types 32 | ~~~~~~~~~~~~~~~ 33 | 34 | .. autoclass:: twython.streaming.types.TwythonStreamerTypes 35 | :inherited-members: 36 | 37 | .. autoclass:: twython.streaming.types.TwythonStreamerTypesStatuses 38 | :inherited-members: 39 | 40 | Exceptions 41 | ---------- 42 | 43 | .. autoexception:: twython.TwythonError 44 | .. autoexception:: twython.TwythonAuthError 45 | .. autoexception:: twython.TwythonRateLimitError -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Twython documentation build configuration file, created by 4 | # sphinx-quickstart on Thu May 30 22:31:25 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | sys.path.insert(0, os.path.abspath('..')) 21 | 22 | import twython 23 | # -- General configuration ----------------------------------------------------- 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be extensions 29 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 30 | extensions = ['sphinx.ext.autodoc'] 31 | 32 | # Add any paths that contain templates here, relative to this directory. 33 | templates_path = ['_templates'] 34 | 35 | # The suffix of source filenames. 36 | source_suffix = '.rst' 37 | 38 | # The encoding of source files. 39 | #source_encoding = 'utf-8-sig' 40 | 41 | # The master toctree document. 42 | master_doc = 'index' 43 | 44 | # General information about the project. 45 | project = u'Twython' 46 | copyright = u'2013, Ryan McGrath' 47 | 48 | # The version info for the project you're documenting, acts as replacement for 49 | # |version| and |release|, also used in various other places throughout the 50 | # built documents. 51 | # 52 | # The short X.Y version. 53 | version = '3.8.0' 54 | # The full version, including alpha/beta/rc tags. 55 | release = '3.8.0' 56 | 57 | # The language for content autogenerated by Sphinx. Refer to documentation 58 | # for a list of supported languages. 59 | #language = None 60 | 61 | # There are two options for replacing |today|: either, you set today to some 62 | # non-false value, then it is used: 63 | #today = '' 64 | # Else, today_fmt is used as the format for a strftime call. 65 | #today_fmt = '%B %d, %Y' 66 | 67 | # List of patterns, relative to source directory, that match files and 68 | # directories to ignore when looking for source files. 69 | exclude_patterns = ['_build'] 70 | 71 | # The reST default role (used for this markup: `text`) to use for all documents. 72 | #default_role = None 73 | 74 | # If true, '()' will be appended to :func: etc. cross-reference text. 75 | #add_function_parentheses = True 76 | 77 | # If true, the current module name will be prepended to all description 78 | # unit titles (such as .. function::). 79 | #add_module_names = True 80 | 81 | # If true, sectionauthor and moduleauthor directives will be shown in the 82 | # output. They are ignored by default. 83 | #show_authors = False 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = 'sphinx' 87 | 88 | # A list of ignored prefixes for module index sorting. 89 | #modindex_common_prefix = [] 90 | 91 | # If true, keep warnings as "system message" paragraphs in the built documents. 92 | #keep_warnings = False 93 | 94 | 95 | # -- Options for HTML output --------------------------------------------------- 96 | 97 | # The theme to use for HTML and HTML Help pages. See the documentation for 98 | # a list of builtin themes. 99 | html_theme = 'default' 100 | 101 | # Theme options are theme-specific and customize the look and feel of a theme 102 | # further. For a list of options available for each theme, see the 103 | # documentation. 104 | #html_theme_options = {} 105 | 106 | # Add any paths that contain custom themes here, relative to this directory. 107 | #html_theme_path = [] 108 | 109 | # The name for this set of Sphinx documents. If None, it defaults to 110 | # " v documentation". 111 | #html_title = None 112 | 113 | # A shorter title for the navigation bar. Default is the same as html_title. 114 | #html_short_title = None 115 | 116 | # The name of an image file (relative to this directory) to place at the top 117 | # of the sidebar. 118 | #html_logo = None 119 | 120 | # The name of an image file (within the static path) to use as favicon of the 121 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 122 | # pixels large. 123 | #html_favicon = None 124 | 125 | # Add any paths that contain custom static files (such as style sheets) here, 126 | # relative to this directory. They are copied after the builtin static files, 127 | # so a file named "default.css" will overwrite the builtin "default.css". 128 | html_static_path = ['_static'] 129 | 130 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 131 | # using the given strftime format. 132 | #html_last_updated_fmt = '%b %d, %Y' 133 | 134 | # If true, SmartyPants will be used to convert quotes and dashes to 135 | # typographically correct entities. 136 | #html_use_smartypants = True 137 | 138 | # Custom sidebar templates, maps document names to template names. 139 | #html_sidebars = {} 140 | 141 | # Additional templates that should be rendered to pages, maps page names to 142 | # template names. 143 | #html_additional_pages = {} 144 | 145 | # If false, no module index is generated. 146 | #html_domain_indices = True 147 | 148 | # If false, no index is generated. 149 | #html_use_index = True 150 | 151 | # If true, the index is split into individual pages for each letter. 152 | #html_split_index = False 153 | 154 | # If true, links to the reST sources are added to the pages. 155 | #html_show_sourcelink = True 156 | 157 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 158 | #html_show_sphinx = True 159 | 160 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 161 | #html_show_copyright = True 162 | 163 | # If true, an OpenSearch description file will be output, and all pages will 164 | # contain a tag referring to it. The value of this option must be the 165 | # base URL from which the finished HTML is served. 166 | #html_use_opensearch = '' 167 | 168 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 169 | #html_file_suffix = None 170 | 171 | # Output file base name for HTML help builder. 172 | htmlhelp_basename = 'Twythondoc' 173 | 174 | 175 | # -- Options for LaTeX output -------------------------------------------------- 176 | 177 | latex_elements = { 178 | # The paper size ('letterpaper' or 'a4paper'). 179 | #'papersize': 'letterpaper', 180 | 181 | # The font size ('10pt', '11pt' or '12pt'). 182 | #'pointsize': '10pt', 183 | 184 | # Additional stuff for the LaTeX preamble. 185 | #'preamble': '', 186 | } 187 | 188 | # Grouping the document tree into LaTeX files. List of tuples 189 | # (source start file, target name, title, author, documentclass [howto/manual]). 190 | latex_documents = [ 191 | ('index', 'Twython.tex', u'Twython Documentation', 192 | u'Ryan McGrath', 'manual'), 193 | ] 194 | 195 | # The name of an image file (relative to this directory) to place at the top of 196 | # the title page. 197 | #latex_logo = None 198 | 199 | # For "manual" documents, if this is true, then toplevel headings are parts, 200 | # not chapters. 201 | #latex_use_parts = False 202 | 203 | # If true, show page references after internal links. 204 | #latex_show_pagerefs = False 205 | 206 | # If true, show URL addresses after external links. 207 | #latex_show_urls = False 208 | 209 | # Documents to append as an appendix to all manuals. 210 | #latex_appendices = [] 211 | 212 | # If false, no module index is generated. 213 | #latex_domain_indices = True 214 | 215 | 216 | # -- Options for manual page output -------------------------------------------- 217 | 218 | # One entry per manual page. List of tuples 219 | # (source start file, name, description, authors, manual section). 220 | man_pages = [ 221 | ('index', 'twython', u'Twython Documentation', 222 | [u'Ryan McGrath'], 1) 223 | ] 224 | 225 | # If true, show URL addresses after external links. 226 | #man_show_urls = False 227 | 228 | 229 | # -- Options for Texinfo output ------------------------------------------------ 230 | 231 | # Grouping the document tree into Texinfo files. List of tuples 232 | # (source start file, target name, title, author, 233 | # dir menu entry, description, category) 234 | texinfo_documents = [ 235 | ('index', 'Twython', u'Twython Documentation', 236 | u'Ryan McGrath', 'Twython', 'One line description of project.', 237 | 'Miscellaneous'), 238 | ] 239 | 240 | 241 | # Activate the theme. 242 | sys.path.append(os.path.abspath('_themes')) 243 | html_theme_path = ['_themes'] 244 | html_theme = 'basicstrap' 245 | html_theme_options = { 246 | 'inner_theme': True, 247 | 'inner_theme_name': 'bootswatch-cerulean', 248 | } 249 | 250 | # Documents to append as an appendix to all manuals. 251 | #texinfo_appendices = [] 252 | 253 | # If false, no module index is generated. 254 | #texinfo_domain_indices = True 255 | 256 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 257 | #texinfo_show_urls = 'footnote' 258 | 259 | # If true, do not generate a @detailmenu in the "Top" node's menu. 260 | #texinfo_no_detailmenu = False 261 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Twython documentation master file, created by 2 | sphinx-quickstart on Thu May 30 22:31:25 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Twython 7 | ======= 8 | 9 | | Actively maintained, pure Python wrapper for the Twitter API. Supports both normal and streaming Twitter APIs 10 | 11 | 12 | Features 13 | -------- 14 | - Query data for: 15 | - User information 16 | - Twitter lists 17 | - Timelines 18 | - Direct Messages 19 | - and anything found in `the Twitter API docs `_. 20 | - Image Uploading: 21 | - Update user status with an image 22 | - Change user avatar 23 | - Change user background image 24 | - Change user banner image 25 | - OAuth 2 Application Only (read-only) Support 26 | - Support for Twitter's Streaming API 27 | - Seamless Python 3 support! 28 | 29 | Usage 30 | ----- 31 | 32 | .. 33 | I know it isn't necessary to start a new tree for every section, 34 | but I think it looks a bit cleaner that way! 35 | 36 | .. toctree:: 37 | :maxdepth: 4 38 | 39 | usage/install 40 | 41 | .. toctree:: 42 | :maxdepth: 4 43 | 44 | usage/starting_out 45 | 46 | .. toctree:: 47 | :maxdepth: 4 48 | 49 | usage/basic_usage 50 | 51 | .. toctree:: 52 | :maxdepth: 4 53 | 54 | usage/advanced_usage 55 | 56 | .. toctree:: 57 | :maxdepth: 4 58 | 59 | usage/streaming_api 60 | 61 | .. toctree:: 62 | :maxdepth: 2 63 | 64 | usage/special_functions 65 | 66 | Twython API Documentation 67 | ------------------------- 68 | 69 | .. toctree:: 70 | :maxdepth: 2 71 | 72 | api 73 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Twython.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Twython.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/usage/advanced_usage.rst: -------------------------------------------------------------------------------- 1 | .. _advanced-usage: 2 | 3 | Advanced Usage 4 | ============== 5 | 6 | This section will cover how to use Twython and interact with some more advanced API calls 7 | 8 | Before you make any API calls, make sure you :ref:`authenticated the user ` (or :ref:`app `)! 9 | 10 | .. note:: All sections on this page will assume you're using a Twython instance 11 | 12 | ******************************************************************************* 13 | 14 | Create a Twython instance with your application keys and the users OAuth tokens 15 | 16 | .. code-block:: python 17 | 18 | from twython import Twython 19 | twitter = Twython(APP_KEY, APP_SECRET, 20 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 21 | 22 | Updating Status with Image 23 | -------------------------- 24 | 25 | This uploads an image as a media object and associates it with a status update. 26 | 27 | .. code-block:: python 28 | 29 | photo = open('/path/to/file/image.jpg', 'rb') 30 | response = twitter.upload_media(media=photo) 31 | twitter.update_status(status='Checkout this cool image!', media_ids=[response['media_id']]) 32 | 33 | Documentation: 34 | 35 | * https://developer.twitter.com/en/docs/api-reference-index 36 | * https://developer.twitter.com/en/docs/media/upload-media/overview 37 | 38 | Updating Status with Video 39 | -------------------------- 40 | 41 | This uploads a video as a media object and associates it with a status update. 42 | 43 | .. code-block:: python 44 | 45 | video = open('/path/to/file/video.mp4', 'rb') 46 | response = twitter.upload_video(media=video, media_type='video/mp4') 47 | twitter.update_status(status='Checkout this cool video!', media_ids=[response['media_id']]) 48 | 49 | Documentation: 50 | 51 | * https://developer.twitter.com/en/docs/api-reference-index 52 | * https://developer.twitter.com/en/docs/media/upload-media/overview 53 | 54 | Posting a Status with an Editing Image 55 | -------------------------------------- 56 | 57 | This example resizes an image, then uploads it as a media object and associates it 58 | with a status update. 59 | 60 | .. code-block:: python 61 | 62 | # Assuming that you are working with a JPEG 63 | 64 | from PIL import Image 65 | from io import BytesIO 66 | 67 | photo = Image.open('/path/to/file/image.jpg') 68 | 69 | basewidth = 320 70 | wpercent = (basewidth / float(photo.size[0])) 71 | height = int((float(photo.size[1]) * float(wpercent))) 72 | photo = photo.resize((basewidth, height), Image.ANTIALIAS) 73 | 74 | image_io = BytesIO() 75 | photo.save(image_io, format='JPEG') 76 | 77 | # If you do not seek(0), the image will be at the end of the file and 78 | # unable to be read 79 | image_io.seek(0) 80 | 81 | response = twitter.upload_media(media=image_io) 82 | twitter.update_status(status='Checkout this cool image!', media_ids=[response['media_id']]) 83 | 84 | 85 | Search Generator 86 | ---------------- 87 | 88 | So, if you're pretty into Python, you probably know about `generators `_ 89 | 90 | That being said, Twython offers a generator for search results and can be accessed by using the following code: 91 | 92 | .. code-block:: python 93 | 94 | from twython import Twython 95 | twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, 96 | OAUTH_TOKEN_SECRET) 97 | 98 | results = twitter.cursor(twitter.search, q='python') 99 | for result in results: 100 | print(result) 101 | 102 | Manipulate the Request (headers, proxies, etc.) 103 | ----------------------------------------------- 104 | 105 | There are times when you may want to turn SSL verification off, send custom headers, or add proxies for the request to go through. 106 | 107 | Twython uses the `requests `_ library to make API calls to Twitter. ``requests`` accepts a few parameters to allow developers to manipulate the acutal HTTP request. 108 | 109 | Here is an example of sending custom headers to a Twitter API request: 110 | 111 | .. code-block:: python 112 | 113 | from twython import Twython 114 | 115 | client_args = { 116 | 'headers': { 117 | 'User-Agent': 'My App Name' 118 | } 119 | } 120 | 121 | twitter = Twython(APP_KEY, APP_SECRET, 122 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET, 123 | client_args=client_args) 124 | 125 | Here is an example of sending the request through proxies: 126 | 127 | .. code-block:: python 128 | 129 | from twython import Twython 130 | 131 | client_args = { 132 | 'proxies': { 133 | 'http': 'http://10.0.10.1:8000', 134 | 'https': 'https://10.0.10.1:8001', 135 | } 136 | } 137 | 138 | twitter = Twython(APP_KEY, APP_SECRET, 139 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET, 140 | client_args=client_args) 141 | 142 | or both (and set a timeout variable): 143 | 144 | .. code-block:: python 145 | 146 | from twython import Twython 147 | 148 | client_args = { 149 | 'headers': { 150 | 'User-Agent': 'My App Name' 151 | }, 152 | 'proxies': { 153 | 'http': 'http://10.0.10.1:8000', 154 | 'https': 'https://10.0.10.1:8001', 155 | } 156 | 'timeout': 300, 157 | } 158 | 159 | twitter = Twython(APP_KEY, APP_SECRET, 160 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET, 161 | client_args=client_args) 162 | 163 | Access Headers of Previous Call 164 | ------------------------------- 165 | 166 | There are times when you may want to check headers from the previous call. 167 | If you wish to access headers (ex. x-rate-limit-remaining, x-rate-limit-reset, content-type), you'll use the ``get_lastfunction_header`` method. 168 | 169 | .. code-block:: python 170 | 171 | from twython import Twython 172 | 173 | twitter = Twython(APP_KEY, APP_SECRET, 174 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 175 | 176 | twitter.get_home_timeline() 177 | twitter.get_lastfunction_header('x-rate-limit-remaining') 178 | 179 | 180 | So now you can authenticate, update your status (with or without an image), search Twitter, and a few other things! Good luck! 181 | -------------------------------------------------------------------------------- /docs/usage/basic_usage.rst: -------------------------------------------------------------------------------- 1 | .. _basic-usage: 2 | 3 | Basic Usage 4 | =========== 5 | 6 | This section will cover how to use Twython and interact with some basic Twitter API calls 7 | 8 | Before you make any API calls, make sure you :ref:`authenticated the user ` (or :ref:`app `)! 9 | 10 | .. note:: All sections on this page will assume you're using a Twython instance 11 | 12 | ******************************************************************************* 13 | 14 | Authenticated Calls 15 | ------------------- 16 | 17 | OAuth 1 18 | ~~~~~~~ 19 | 20 | Create a Twython instance with your application keys and the users OAuth tokens 21 | 22 | .. code-block:: python 23 | 24 | from twython import Twython 25 | twitter = Twython(APP_KEY, APP_SECRET, 26 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 27 | 28 | User Information 29 | ^^^^^^^^^^^^^^^^ 30 | 31 | Documentation: https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials 32 | 33 | .. code-block:: python 34 | 35 | twitter.verify_credentials() 36 | 37 | Authenticated Users Home Timeline 38 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 39 | 40 | Documentation: https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline 41 | 42 | .. code-block:: python 43 | 44 | twitter.get_home_timeline() 45 | 46 | Updating Status 47 | ^^^^^^^^^^^^^^^ 48 | 49 | This method makes use of dynamic arguments, :ref:`read more about them ` 50 | 51 | Documentation: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update 52 | 53 | .. code-block:: python 54 | 55 | twitter.update_status(status='See how easy using Twython is!') 56 | 57 | 58 | OAuth 2 59 | ~~~~~~~ 60 | 61 | Create a Twython instance with your application key and access token 62 | 63 | .. code-block:: python 64 | 65 | from twython import Twython 66 | twitter = Twython(APP_KEY, access_token=ACCESS_TOKEN) 67 | 68 | .. _howtosearch: 69 | 70 | Searching 71 | --------- 72 | 73 | .. note:: Searching can be done whether you're authenticated via OAuth 1 or OAuth 2 74 | 75 | Documentation: https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets 76 | 77 | .. code-block:: python 78 | 79 | twitter.search(q='python') 80 | 81 | .. _dynamicargexplaination: 82 | 83 | .. important:: To help explain :ref:`dynamic function arguments ` a little more, you can see that the previous call used the keyword argument ``q``, that is because Twitter specifies in their `search documentation `_ that the search call accepts the parameter "q". You can pass mutiple keyword arguments. The search documentation also specifies that the call accepts the parameter "result_type" 84 | 85 | .. code-block:: python 86 | 87 | twitter.search(q='python', result_type='popular') 88 | 89 | ******************************************************************************* 90 | 91 | So, now, you're pretty well versed on making authenticated calls to Twitter using Twython. Check out the :ref:`advanced usage ` section, for some functions that may be a little more complicated. 92 | -------------------------------------------------------------------------------- /docs/usage/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Installation 4 | ============ 5 | 6 | Information on how to properly install Twython 7 | 8 | ******************************************************************************* 9 | 10 | Pip or Easy Install 11 | ------------------- 12 | 13 | Install Twython via `pip `_ 14 | 15 | .. code-block:: bash 16 | 17 | $ pip install twython 18 | 19 | or, with `easy_install `_ 20 | 21 | .. code-block:: bash 22 | 23 | $ easy_install twython 24 | 25 | But, hey... `that's up to you `_. 26 | 27 | 28 | Source Code 29 | ----------- 30 | 31 | Twython is actively maintained on GitHub 32 | 33 | Feel free to clone the repository 34 | 35 | .. code-block:: bash 36 | 37 | git clone git://github.com/ryanmcgrath/twython.git 38 | 39 | `tarball `_ 40 | 41 | .. code-block:: bash 42 | 43 | $ curl -OL https://github.com/ryanmcgrath/twython/tarball/master 44 | 45 | `zipball `_ 46 | 47 | .. code-block:: bash 48 | 49 | $ curl -OL https://github.com/ryanmcgrath/twython/zipball/master 50 | 51 | Now that you have the source code, install it into your site-packages directory 52 | 53 | .. code-block:: bash 54 | 55 | $ python setup.py install 56 | 57 | ******************************************************************************* 58 | 59 | So Twython is installed! Now, head over to the :ref:`starting out ` section. -------------------------------------------------------------------------------- /docs/usage/special_functions.rst: -------------------------------------------------------------------------------- 1 | .. special-functions: 2 | 3 | Special Functions 4 | ================= 5 | 6 | This section covers methods to are part of Twython but not necessarily connected to the Twitter API. 7 | 8 | ******************************************************************************* 9 | 10 | Cursor 11 | ------ 12 | 13 | This function returns a generator for Twitter API endpoints that are able to be pagintated in some way (either by cursor or since_id parameter) 14 | 15 | The Old Way 16 | ^^^^^^^^^^^ 17 | 18 | .. code-block:: python 19 | 20 | from twython import Twython 21 | 22 | twitter = Twython(APP_KEY, APP_SECRET, 23 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 24 | 25 | results = twitter.search(q='twitter') 26 | if results.get('statuses'): 27 | for result in results['statuses']: 28 | print(result['id_str']) 29 | 30 | The New Way 31 | ^^^^^^^^^^^ 32 | 33 | .. code-block:: python 34 | 35 | from twython import Twython 36 | 37 | twitter = Twython(APP_KEY, APP_SECRET, 38 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 39 | 40 | results = twitter.cursor(twitter.search, q='twitter') 41 | for result in results: 42 | print(result['id_str']) 43 | 44 | Another example: 45 | 46 | .. code-block:: python 47 | 48 | results = twitter.cursor(twitter.get_mentions_timeline) 49 | for result in results: 50 | print(result['id_str']) 51 | 52 | Items vs Pages 53 | ^^^^^^^^^^^^^^ 54 | 55 | 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. 56 | 57 | .. code-block:: python 58 | 59 | results = twitter.cursor(twitter.get_mentions_timeline, return_pages=True) 60 | # page is a list 61 | for page in results: 62 | for result in page: 63 | print(result['id_str']) 64 | 65 | 66 | HTML for Tweet 67 | -------------- 68 | 69 | This function takes a tweet object received from the Twitter API and returns an string formatted in HTML with the links, user mentions and hashtags replaced. 70 | 71 | .. code-block:: python 72 | 73 | from twython import Twython 74 | 75 | twitter = Twython(APP_KEY, APP_SECRET, 76 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 77 | 78 | user_tweets = twitter.get_user_timeline(screen_name='mikehelmick', 79 | include_rts=True) 80 | for tweet in user_tweets: 81 | tweet['text'] = Twython.html_for_tweet(tweet) 82 | print(tweet['text']) 83 | 84 | The above code takes all the tweets from a specific users timeline, loops over them and replaces the value of ``tweet['text']`` with HTML. 85 | 86 | So: 87 | 88 | http://t.co/FCmXyI6VHd is #cool, lol! @mikehelmick shd #checkitout. Love, @__twython__ $IBM https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71 89 | 90 | will be replaced with: 91 | 92 | google.com is #cool, lol! @mikehelmick shd #checkitout. Love, @__twython__ $IBM github.com pic.twitter.com/N6InAO4B71 93 | 94 | .. note:: When converting the string to HTML we add a class to each HTML tag so that you can maninpulate the DOM later on. 95 | 96 | - For urls that are replaced we add ``class="twython-url"`` to the anchor tag 97 | - For media urls that are replaced we add ``class="twython-media"`` to the anchor tag 98 | - For user mentions that are replaced we add ``class="twython-mention"`` to the anchor tag 99 | - For hashtags that are replaced we add ``class="twython-hashtag"`` to the anchor tag 100 | - For symbols that are replaced we add ``class="twython-symbol"`` to the anchor tag 101 | 102 | This function accepts two parameters: ``use_display_url`` and ``use_expanded_url`` 103 | By default, ``use_display_url`` is ``True``. Meaning the link displayed in the tweet text will appear as (ex. google.com, github.com) 104 | 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) 105 | If ``use_display_url`` and ``use_expanded_url`` are ``False``, short url will be used (t.co/xxxxx) 106 | -------------------------------------------------------------------------------- /docs/usage/starting_out.rst: -------------------------------------------------------------------------------- 1 | .. _starting-out: 2 | 3 | Starting Out 4 | ============ 5 | 6 | This section is going to help you understand creating a Twitter Application, authenticating a user, and making basic API calls 7 | 8 | ******************************************************************************* 9 | 10 | Beginning 11 | --------- 12 | 13 | First, you'll want to head over to https://apps.twitter.com/ and register an application! 14 | 15 | After you register, grab your applications ``Consumer Key`` and ``Consumer Secret`` from the application details tab. 16 | 17 | Now you're ready to start authentication! 18 | 19 | Authentication 20 | -------------- 21 | 22 | Twython offers support for both OAuth 1 and OAuth 2 authentication. 23 | 24 | The difference: 25 | 26 | - :ref:`OAuth 1 ` is for user authenticated calls (tweeting, following people, sending DMs, etc.) 27 | - :ref:`OAuth 2 ` is for application authenticated calls (when you don't want to authenticate a user and make read-only calls to Twitter, i.e. searching, reading a public users timeline) 28 | 29 | .. _oauth1: 30 | 31 | OAuth 1 (User Authentication) 32 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 33 | 34 | .. important:: Again, if your web app is planning on using interacting with users, this **IS** the authentication type for you. If you're not interested in authenticating a user and plan on making read-only calls, check out the :ref:`OAuth 2 ` section. 35 | 36 | First, you'll want to import Twython 37 | 38 | .. code-block:: python 39 | 40 | from twython import Twython 41 | 42 | Now, you'll want to create a Twython instance with your ``Consumer Key`` and ``Consumer Secret`` 43 | 44 | Obtain Authorization URL 45 | ^^^^^^^^^^^^^^^^^^^^^^^^ 46 | 47 | .. note:: Only pass *callback_url* to *get_authentication_tokens* if your application is a Web Application 48 | 49 | Desktop and Mobile Applications **do not** require a callback_url 50 | 51 | .. code-block:: python 52 | 53 | APP_KEY = 'YOUR_APP_KEY' 54 | APP_SECRET = 'YOUR_APP_SECRET' 55 | 56 | twitter = Twython(APP_KEY, APP_SECRET) 57 | auth = twitter.get_authentication_tokens(callback_url='http://mysite.com/callback') 58 | 59 | From the ``auth`` variable, save the ``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 60 | 61 | .. code-block:: python 62 | 63 | OAUTH_TOKEN = auth['oauth_token'] 64 | OAUTH_TOKEN_SECRET = auth['oauth_token_secret'] 65 | 66 | Send the user to the authentication url, you can obtain it by accessing 67 | 68 | .. code-block:: python 69 | 70 | auth['auth_url'] 71 | 72 | Handling the Callback 73 | ^^^^^^^^^^^^^^^^^^^^^ 74 | 75 | .. note:: If your application is a Desktop or Mobile Application *oauth_verifier* will be the PIN code 76 | 77 | After they authorize your application to access some of their account details, they'll be redirected to the callback url you specified in ``get_autentication_tokens`` 78 | 79 | You'll want to extract the ``oauth_verifier`` from the url. 80 | 81 | Django example: 82 | 83 | .. code-block:: python 84 | 85 | oauth_verifier = request.GET['oauth_verifier'] 86 | 87 | 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 88 | 89 | .. code-block:: python 90 | 91 | twitter = Twython(APP_KEY, APP_SECRET, 92 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 93 | 94 | final_step = twitter.get_authorized_tokens(oauth_verifier) 95 | 96 | Once you have the final user tokens, store them in a database for later use! 97 | 98 | .. code-block:: python 99 | 100 | OAUTH_TOKEN = final_step['oauth_token'] 101 | OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] 102 | 103 | .. _oauth2: 104 | 105 | OAuth 2 (Application Authentication) 106 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 107 | 108 | .. attention:: Just a reminder, this authentication type is for when you don't want to authenticate and interact with users and make read-only calls to Twitter 109 | 110 | OAuth 2 authentication is 100x easier than OAuth 1. 111 | Let's say you *just* made your application and have your ``Consumer Key`` and ``Consumer Secret`` 112 | 113 | First, you'll want to import Twython 114 | 115 | .. code-block:: python 116 | 117 | from twython import Twython 118 | 119 | Obtain an OAuth 2 Access Token 120 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 121 | 122 | .. code-block:: python 123 | 124 | APP_KEY = 'YOUR_APP_KEY' 125 | APP_SECRET = 'YOUR_APP_SECRET' 126 | 127 | twitter = Twython(APP_KEY, APP_SECRET, oauth_version=2) 128 | ACCESS_TOKEN = twitter.obtain_access_token() 129 | 130 | Save ``ACCESS_TOKEN`` in a database or something for later use! 131 | 132 | Use the Access Token 133 | ^^^^^^^^^^^^^^^^^^^^ 134 | 135 | .. code-block:: python 136 | 137 | APP_KEY = 'YOUR_APP_KEY' 138 | ACCESS_TOKEN = 'YOUR_ACCESS_TOKEN' 139 | 140 | twitter = Twython(APP_KEY, access_token=ACCESS_TOKEN) 141 | 142 | Now that you have your OAuth 2 access_token, maybe you'll want to perform a :ref:`search ` or something 143 | 144 | The Twython API Table 145 | --------------------- 146 | 147 | The Twython package contains a file ``endpoints.py`` which holds a Mixin of all Twitter API endpoints. This is so Twython's core ``api.py`` isn't cluttered with 50+ methods. 148 | 149 | .. _dynamicfunctionarguments: 150 | 151 | Dynamic Function Arguments 152 | -------------------------- 153 | 154 | 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. 155 | 156 | What Twython Returns 157 | -------------------- 158 | 159 | Twython returns native Python objects. We convert the JSON sent to us from Twitter to an object so you don't have to. 160 | 161 | 162 | ******************************************************************************* 163 | 164 | Now that you have a little idea of the type of data you'll be receiving, briefed on how arguments are handled, and your application tokens and user oauth tokens (or access token if you're using OAuth 2), check out the :ref:`basic usage ` section. 165 | -------------------------------------------------------------------------------- /docs/usage/streaming_api.rst: -------------------------------------------------------------------------------- 1 | .. _streaming-api: 2 | 3 | Streaming API 4 | ============= 5 | 6 | This section will cover how to use Twython and interact with the Twitter Streaming API. 7 | 8 | Streaming Documentation: https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/streaming-message-types 9 | 10 | .. important:: The Streaming API requires that you have OAuth 1 authentication credentials. If you don't have credentials, head over to the :ref:`authentication section ` and find out how! 11 | 12 | Setting Up Your Streamer 13 | ------------------------ 14 | 15 | .. note:: When stream data is sent back to Twython, we send the data through signals (i.e. ``on_success``, ``on_error``, etc.) 16 | 17 | Make sure you import ``TwythonStreamer`` 18 | 19 | .. code-block:: python 20 | 21 | from twython import TwythonStreamer 22 | 23 | Now set up how you want to handle the signals. 24 | 25 | .. code-block:: python 26 | 27 | class MyStreamer(TwythonStreamer): 28 | def on_success(self, data): 29 | if 'text' in data: 30 | print(data['text']) 31 | 32 | def on_error(self, status_code, data): 33 | print(status_code) 34 | 35 | # Want to stop trying to get data because of the error? 36 | # Uncomment the next line! 37 | # self.disconnect() 38 | 39 | More signals that you can extend on can be found in the Developer Interface section under :ref:`Streaming Interface ` 40 | 41 | Filtering Public Statuses 42 | ------------------------- 43 | 44 | .. code-block:: python 45 | 46 | stream = MyStreamer(APP_KEY, APP_SECRET, 47 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 48 | stream.statuses.filter(track='twitter') 49 | 50 | With the code above, data should be flowing in. 51 | 52 | -------------------------------------------------------------------------------- /examples/follow_user.py: -------------------------------------------------------------------------------- 1 | from twython import Twython, TwythonError 2 | 3 | # Optionally accept user data from the command line (or elsewhere). 4 | # 5 | # Usage: follow_user.py ryanmcgrath 6 | 7 | import sys 8 | 9 | if len(sys.argv) >= 2: 10 | target = sys.argv[1] 11 | else: 12 | target = raw_input("User to follow: ") 13 | # For Python 3.x use: target = input("User to follow: ") 14 | 15 | # Requires Authentication as of Twitter API v1.1 16 | twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 17 | 18 | try: 19 | twitter.create_friendship(screen_name=target, follow="true") 20 | except TwythonError as e: 21 | print(e) 22 | -------------------------------------------------------------------------------- /examples/get_direct_messages.py: -------------------------------------------------------------------------------- 1 | from twython import Twython, TwythonError 2 | twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 3 | 4 | get_list = twitter.get_direct_messages() 5 | print(get_list) 6 | -------------------------------------------------------------------------------- /examples/get_user_timeline.py: -------------------------------------------------------------------------------- 1 | from twython import Twython, TwythonError 2 | 3 | # Requires Authentication as of Twitter API v1.1 4 | twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 5 | try: 6 | user_timeline = twitter.get_user_timeline(screen_name='ryanmcgrath') 7 | except TwythonError as e: 8 | print e 9 | 10 | print(user_timeline) 11 | -------------------------------------------------------------------------------- /examples/search_results.py: -------------------------------------------------------------------------------- 1 | from twython import Twython, TwythonError 2 | 3 | # Requires Authentication as of Twitter API v1.1 4 | twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 5 | try: 6 | search_results = twitter.search(q='WebsDotCom', count=50) 7 | except TwythonError as e: 8 | print e 9 | 10 | for tweet in search_results['statuses']: 11 | print 'Tweet from @%s Date: %s' % (tweet['user']['screen_nam\ 12 | e'].encode('utf-8'), 13 | tweet['created_at']) 14 | print tweet['text'].encode('utf-8'), '\n' 15 | -------------------------------------------------------------------------------- /examples/stream.py: -------------------------------------------------------------------------------- 1 | from twython import TwythonStreamer 2 | 3 | 4 | class MyStreamer(TwythonStreamer): 5 | def on_success(self, data): 6 | if 'text' in data: 7 | print data['text'].encode('utf-8') 8 | # Want to disconnect after the first result? 9 | # self.disconnect() 10 | 11 | def on_error(self, status_code, data): 12 | print status_code, data 13 | 14 | # Requires Authentication as of Twitter API v1.1 15 | stream = MyStreamer(APP_KEY, APP_SECRET, 16 | OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 17 | 18 | stream.statuses.filter(track='twitter') 19 | # stream.user() 20 | # Read the authenticated users home timeline 21 | # (what they see on Twitter) in real-time 22 | # stream.site(follow='twitter') 23 | -------------------------------------------------------------------------------- /examples/update_profile_image.py: -------------------------------------------------------------------------------- 1 | from twython import Twython 2 | 3 | # Requires Authentication as of Twitter API v1.1 4 | twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 5 | 6 | avatar = open('myImage.png', 'rb') 7 | twitter.update_profile_image(image=avatar) 8 | -------------------------------------------------------------------------------- /examples/update_status.py: -------------------------------------------------------------------------------- 1 | from twython import Twython, TwythonError 2 | 3 | # Requires Authentication as of Twitter API v1.1 4 | twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 5 | 6 | try: 7 | twitter.update_status(status='See how easy this was?') 8 | except TwythonError as e: 9 | print e 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | coverage==3.6.0 2 | requests>=2.1.0 3 | requests_oauthlib>=0.4.0 4 | python-coveralls==2.1.0 5 | nose-cov==1.6 6 | responses==0.3.0 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | try: 7 | from setuptools import setup 8 | except ImportError: 9 | from distutils.core import setup 10 | 11 | __author__ = 'Ryan McGrath ' 12 | __version__ = '3.9.1' 13 | 14 | packages = [ 15 | 'twython', 16 | 'twython.streaming' 17 | ] 18 | 19 | if sys.argv[-1] == 'publish': 20 | os.system('python setup.py sdist upload') 21 | sys.exit() 22 | 23 | setup( 24 | name='twython', 25 | version=__version__, 26 | install_requires=['requests>=2.1.0', 'requests_oauthlib>=0.4.0'], 27 | python_requires='>=3.5', 28 | author='Ryan McGrath', 29 | author_email='ryan@rymc.io', 30 | license='MIT', 31 | url='https://github.com/ryanmcgrath/twython/tree/master', 32 | keywords='twitter search api tweet twython stream', 33 | description='Actively maintained, pure Python wrapper for the Twitter API. Supports both normal and streaming Twitter APIs', 34 | long_description=open('README.md', encoding='utf-8').read() + '\n\n' +open('HISTORY.md', encoding='utf-8').read(), 35 | long_description_content_type='text/markdown', 36 | include_package_data=True, 37 | packages=packages, 38 | classifiers=[ 39 | 'Development Status :: 4 - Beta', 40 | 'Intended Audience :: Developers', 41 | 'License :: OSI Approved :: MIT License', 42 | 'Topic :: Software Development :: Libraries :: Python Modules', 43 | 'Topic :: Communications :: Chat', 44 | 'Topic :: Internet', 45 | 'Programming Language :: Python :: 3', 46 | 'Programming Language :: Python :: 3 :: Only', 47 | 'Programming Language :: Python :: 3.5', 48 | 'Programming Language :: Python :: 3.6', 49 | 'Programming Language :: Python :: 3.7', 50 | ] 51 | ) 52 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanmcgrath/twython/0c405604285364457f3c309969f11ba68163bd05/tests/__init__.py -------------------------------------------------------------------------------- /tests/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | 4 | import sys 5 | import unittest 6 | 7 | app_key = os.environ.get('APP_KEY') 8 | app_secret = os.environ.get('APP_SECRET') 9 | oauth_token = os.environ.get('OAUTH_TOKEN') 10 | oauth_token_secret = os.environ.get('OAUTH_TOKEN_SECRET') 11 | access_token = os.environ.get('ACCESS_TOKEN') 12 | 13 | screen_name = os.environ.get('SCREEN_NAME', '__twython__') 14 | 15 | # Protected Account you ARE following and they ARE following you 16 | protected_twitter_1 = os.environ.get('PROTECTED_TWITTER_1', 'TwythonSecure1') 17 | 18 | # Protected Account you ARE NOT following 19 | protected_twitter_2 = os.environ.get('PROTECTED_TWITTER_2', 'TwythonSecure2') 20 | 21 | # Test Ids 22 | test_tweet_id = os.environ.get('TEST_TWEET_ID', '318577428610031617') 23 | test_list_slug = os.environ.get('TEST_LIST_SLUG', 'team') 24 | test_list_owner_screen_name = os.environ.get('TEST_LIST_OWNER_SCREEN_NAME', 25 | 'twitterapi') 26 | 27 | -------------------------------------------------------------------------------- /tests/test_auth.py: -------------------------------------------------------------------------------- 1 | from twython import Twython, TwythonError, TwythonAuthError 2 | 3 | from .config import app_key, app_secret, screen_name, unittest 4 | 5 | 6 | class TwythonAuthTestCase(unittest.TestCase): 7 | def setUp(self): 8 | self.api = Twython(app_key, app_secret) 9 | self.bad_api = Twython('BAD_APP_KEY', 'BAD_APP_SECRET') 10 | self.bad_api_invalid_tokens = Twython('BAD_APP_KEY', 'BAD_APP_SECRET', 11 | 'BAD_OT', 'BAD_OTS') 12 | 13 | self.oauth2_api = Twython(app_key, app_secret, oauth_version=2) 14 | self.oauth2_bad_api = Twython('BAD_APP_KEY', 'BAD_APP_SECRET', 15 | oauth_version=2) 16 | 17 | @unittest.skip('skipping non-updated test') 18 | def test_get_authentication_tokens(self): 19 | """Test getting authentication tokens works""" 20 | self.api.get_authentication_tokens(callback_url='http://google.com/', 21 | force_login=True, 22 | screen_name=screen_name) 23 | 24 | @unittest.skip('skipping non-updated test') 25 | def test_get_authentication_tokens_bad_tokens(self): 26 | """Test getting authentication tokens with bad tokens 27 | raises TwythonAuthError""" 28 | self.assertRaises(TwythonAuthError, self.bad_api.get_authentication_tokens, 29 | callback_url='http://google.com/') 30 | 31 | @unittest.skip('skipping non-updated test') 32 | def test_get_authorized_tokens_bad_tokens(self): 33 | """Test getting final tokens fails with wrong tokens""" 34 | self.assertRaises(TwythonError, self.bad_api.get_authorized_tokens, 35 | 'BAD_OAUTH_VERIFIER') 36 | 37 | @unittest.skip('skipping non-updated test') 38 | def test_get_authorized_tokens_invalid_or_expired_tokens(self): 39 | """Test getting final token fails when invalid or expired tokens have been passed""" 40 | self.assertRaises(TwythonError, self.bad_api_invalid_tokens.get_authorized_tokens, 41 | 'BAD_OAUTH_VERIFIER') 42 | 43 | @unittest.skip('skipping non-updated test') 44 | def test_get_authentication_tokens_raises_error_when_oauth2(self): 45 | """Test when API is set for OAuth 2, get_authentication_tokens raises 46 | a TwythonError""" 47 | self.assertRaises(TwythonError, self.oauth2_api.get_authentication_tokens) 48 | 49 | @unittest.skip('skipping non-updated test') 50 | def test_get_authorization_tokens_raises_error_when_oauth2(self): 51 | """Test when API is set for OAuth 2, get_authorized_tokens raises 52 | a TwythonError""" 53 | self.assertRaises(TwythonError, self.oauth2_api.get_authorized_tokens, 54 | 'BAD_OAUTH_VERIFIER') 55 | 56 | @unittest.skip('skipping non-updated test') 57 | def test_obtain_access_token(self): 58 | """Test obtaining an Application Only OAuth 2 access token succeeds""" 59 | self.oauth2_api.obtain_access_token() 60 | 61 | @unittest.skip('skipping non-updated test') 62 | def test_obtain_access_token_bad_tokens(self): 63 | """Test obtaining an Application Only OAuth 2 access token using bad app tokens fails""" 64 | self.assertRaises(TwythonAuthError, 65 | self.oauth2_bad_api.obtain_access_token) 66 | 67 | @unittest.skip('skipping non-updated test') 68 | def test_obtain_access_token_raises_error_when_oauth1(self): 69 | """Test when API is set for OAuth 1, obtain_access_token raises a 70 | TwythonError""" 71 | self.assertRaises(TwythonError, self.api.obtain_access_token) 72 | -------------------------------------------------------------------------------- /tests/test_core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from twython import Twython, TwythonError, TwythonAuthError, TwythonRateLimitError 3 | 4 | from .config import unittest 5 | 6 | import responses 7 | import requests 8 | 9 | from twython.compat import is_py2 10 | if is_py2: 11 | from StringIO import StringIO 12 | else: 13 | from io import StringIO 14 | 15 | try: 16 | import unittest.mock as mock 17 | except ImportError: 18 | import mock 19 | 20 | 21 | class TwythonAPITestCase(unittest.TestCase): 22 | def setUp(self): 23 | self.api = Twython('', '', '', '') 24 | 25 | def get_url(self, endpoint): 26 | """Convenience function for mapping from endpoint to URL""" 27 | return '%s/%s.json' % (self.api.api_url % self.api.api_version, endpoint) 28 | 29 | def register_response(self, method, url, body='{}', match_querystring=False, 30 | status=200, adding_headers=None, stream=False, 31 | content_type='application/json; charset=utf-8'): 32 | """Wrapper function for responses for simpler unit tests""" 33 | 34 | # responses uses BytesIO to hold the body so it needs to be in bytes 35 | if not is_py2: 36 | body = bytes(body, 'UTF-8') 37 | 38 | responses.add(method, url, body, match_querystring, 39 | status, adding_headers, stream, content_type) 40 | 41 | @responses.activate 42 | def test_request_should_handle_full_endpoint(self): 43 | """Test that request() accepts a full URL for the endpoint argument""" 44 | url = 'https://api.twitter.com/1.1/search/tweets.json' 45 | self.register_response(responses.GET, url) 46 | 47 | self.api.request(url) 48 | 49 | self.assertEqual(1, len(responses.calls)) 50 | self.assertEqual(url, responses.calls[0].request.url) 51 | 52 | @responses.activate 53 | def test_request_should_handle_relative_endpoint(self): 54 | """Test that request() accepts a twitter endpoint name for the endpoint argument""" 55 | url = 'https://api.twitter.com/1.1/search/tweets.json' 56 | self.register_response(responses.GET, url) 57 | 58 | self.api.request('search/tweets', version='1.1') 59 | 60 | self.assertEqual(1, len(responses.calls)) 61 | self.assertEqual(url, responses.calls[0].request.url) 62 | 63 | @responses.activate 64 | def test_request_should_post_request_regardless_of_case(self): 65 | """Test that request() accepts the HTTP method name regardless of case""" 66 | url = 'https://api.twitter.com/1.1/statuses/update.json' 67 | self.register_response(responses.POST, url) 68 | 69 | self.api.request(url, method='POST') 70 | self.api.request(url, method='post') 71 | 72 | self.assertEqual(2, len(responses.calls)) 73 | self.assertEqual('POST', responses.calls[0].request.method) 74 | self.assertEqual('POST', responses.calls[1].request.method) 75 | 76 | @responses.activate 77 | def test_request_should_throw_exception_with_invalid_http_method(self): 78 | """Test that request() throws an exception when an invalid HTTP method is passed""" 79 | # TODO(cash): should Twython catch the AttributeError and throw a TwythonError 80 | self.assertRaises(AttributeError, self.api.request, endpoint='search/tweets', method='INVALID') 81 | 82 | @responses.activate 83 | def test_request_should_encode_boolean_as_lowercase_string(self): 84 | """Test that request() encodes a boolean parameter as a lowercase string""" 85 | endpoint = 'search/tweets' 86 | url = self.get_url(endpoint) 87 | self.register_response(responses.GET, url) 88 | 89 | self.api.request(endpoint, params={'include_entities': True}) 90 | self.api.request(endpoint, params={'include_entities': False}) 91 | 92 | self.assertEqual(url + '?include_entities=true', responses.calls[0].request.url) 93 | self.assertEqual(url + '?include_entities=false', responses.calls[1].request.url) 94 | 95 | @responses.activate 96 | def test_request_should_handle_string_or_number_parameter(self): 97 | """Test that request() encodes a numeric or string parameter correctly""" 98 | endpoint = 'search/tweets' 99 | url = self.get_url(endpoint) 100 | self.register_response(responses.GET, url) 101 | 102 | self.api.request(endpoint, params={'lang': 'es'}) 103 | self.api.request(endpoint, params={'count': 50}) 104 | 105 | self.assertEqual(url + '?lang=es', responses.calls[0].request.url) 106 | self.assertEqual(url + '?count=50', responses.calls[1].request.url) 107 | 108 | @responses.activate 109 | def test_request_should_encode_list_of_strings_as_string(self): 110 | """Test that request() encodes a list of strings as a comma-separated string""" 111 | endpoint = 'search/tweets' 112 | url = self.get_url(endpoint) 113 | location = ['37.781157', '-122.39872', '1mi'] 114 | self.register_response(responses.GET, url) 115 | 116 | self.api.request(endpoint, params={'geocode': location}) 117 | 118 | # requests url encodes the parameters so , is %2C 119 | self.assertEqual(url + '?geocode=37.781157%2C-122.39872%2C1mi', responses.calls[0].request.url) 120 | 121 | @responses.activate 122 | def test_request_should_encode_numeric_list_as_string(self): 123 | """Test that request() encodes a list of numbers as a comma-separated string""" 124 | endpoint = 'search/tweets' 125 | url = self.get_url(endpoint) 126 | location = [37.781157, -122.39872, '1mi'] 127 | self.register_response(responses.GET, url) 128 | 129 | self.api.request(endpoint, params={'geocode': location}) 130 | 131 | self.assertEqual(url + '?geocode=37.781157%2C-122.39872%2C1mi', responses.calls[0].request.url) 132 | 133 | @responses.activate 134 | def test_request_should_ignore_bad_parameter(self): 135 | """Test that request() ignores unexpected parameter types""" 136 | endpoint = 'search/tweets' 137 | url = self.get_url(endpoint) 138 | self.register_response(responses.GET, url) 139 | 140 | self.api.request(endpoint, params={'geocode': self}) 141 | 142 | self.assertEqual(url, responses.calls[0].request.url) 143 | 144 | @responses.activate 145 | def test_request_should_handle_file_as_parameter(self): 146 | """Test that request() pulls a file out of params for requests lib""" 147 | endpoint = 'account/update_profile_image' 148 | url = self.get_url(endpoint) 149 | self.register_response(responses.POST, url) 150 | 151 | mock_file = StringIO("Twython test image") 152 | self.api.request(endpoint, method='POST', params={'image': mock_file}) 153 | 154 | self.assertIn(b'filename="image"', responses.calls[0].request.body) 155 | self.assertIn(b"Twython test image", responses.calls[0].request.body) 156 | 157 | @responses.activate 158 | def test_request_should_put_params_in_body_when_post(self): 159 | """Test that request() passes params as data when the request is a POST""" 160 | endpoint = 'statuses/update' 161 | url = self.get_url(endpoint) 162 | self.register_response(responses.POST, url) 163 | 164 | self.api.request(endpoint, method='POST', params={'status': 'this is a test'}) 165 | 166 | self.assertIn(b'status=this+is+a+test', responses.calls[0].request.body) 167 | self.assertNotIn('status=this+is+a+test', responses.calls[0].request.url) 168 | 169 | @responses.activate 170 | def test_get_uses_get_method(self): 171 | """Test Twython generic GET request works""" 172 | endpoint = 'account/verify_credentials' 173 | url = self.get_url(endpoint) 174 | self.register_response(responses.GET, url) 175 | 176 | self.api.get(endpoint) 177 | 178 | self.assertEqual(1, len(responses.calls)) 179 | self.assertEqual(url, responses.calls[0].request.url) 180 | 181 | @responses.activate 182 | def test_post_uses_post_method(self): 183 | """Test Twython generic POST request works""" 184 | endpoint = 'statuses/update' 185 | url = self.get_url(endpoint) 186 | self.register_response(responses.POST, url) 187 | 188 | self.api.post(endpoint, params={'status': 'I love Twython!'}) 189 | 190 | self.assertEqual(1, len(responses.calls)) 191 | self.assertEqual(url, responses.calls[0].request.url) 192 | 193 | def test_raise_twython_error_on_request_exception(self): 194 | """Test if TwythonError is raised by a RequestException""" 195 | with mock.patch.object(requests.Session, 'get') as get_mock: 196 | # mocking an ssl cert error 197 | get_mock.side_effect = requests.RequestException("hostname 'example.com' doesn't match ...") 198 | self.assertRaises(TwythonError, self.api.get, 'https://example.com') 199 | 200 | @responses.activate 201 | def test_request_should_get_convert_json_to_data(self): 202 | """Test that Twython converts JSON data to a Python object""" 203 | endpoint = 'statuses/show' 204 | url = self.get_url(endpoint) 205 | self.register_response(responses.GET, url, body='{"id": 210462857140252672}') 206 | 207 | data = self.api.request(endpoint, params={'id': 210462857140252672}) 208 | 209 | self.assertEqual({'id': 210462857140252672}, data) 210 | 211 | @responses.activate 212 | def test_request_should_raise_exception_with_invalid_json(self): 213 | """Test that Twython handles invalid JSON (though Twitter should not return it)""" 214 | endpoint = 'statuses/show' 215 | url = self.get_url(endpoint) 216 | self.register_response(responses.GET, url, body='{"id: 210462857140252672}') 217 | 218 | self.assertRaises(TwythonError, self.api.request, endpoint, params={'id': 210462857140252672}) 219 | 220 | @responses.activate 221 | def test_request_should_handle_401(self): 222 | """Test that Twython raises an auth error on 401 error""" 223 | endpoint = 'statuses/home_timeline' 224 | url = self.get_url(endpoint) 225 | self.register_response(responses.GET, url, body='{"errors":[{"message":"Error"}]}', status=401) 226 | 227 | self.assertRaises(TwythonAuthError, self.api.request, endpoint) 228 | 229 | @responses.activate 230 | def test_request_should_handle_400_for_missing_auth_data(self): 231 | """Test that Twython raises an auth error on 400 error when no oauth data sent""" 232 | endpoint = 'statuses/home_timeline' 233 | url = self.get_url(endpoint) 234 | self.register_response(responses.GET, url, 235 | body='{"errors":[{"message":"Bad Authentication data"}]}', status=400) 236 | 237 | self.assertRaises(TwythonAuthError, self.api.request, endpoint) 238 | 239 | @responses.activate 240 | def test_request_should_handle_400_that_is_not_auth_related(self): 241 | """Test that Twython raises a normal error on 400 error when unrelated to authorization""" 242 | endpoint = 'statuses/home_timeline' 243 | url = self.get_url(endpoint) 244 | self.register_response(responses.GET, url, 245 | body='{"errors":[{"message":"Bad request"}]}', status=400) 246 | 247 | self.assertRaises(TwythonError, self.api.request, endpoint) 248 | 249 | @responses.activate 250 | def test_request_should_handle_rate_limit(self): 251 | """Test that Twython raises an rate limit error on 429""" 252 | endpoint = 'statuses/home_timeline' 253 | url = self.get_url(endpoint) 254 | self.register_response(responses.GET, url, 255 | body='{"errors":[{"message":"Rate Limit"}]}', status=429) 256 | 257 | self.assertRaises(TwythonRateLimitError, self.api.request, endpoint) 258 | 259 | @responses.activate 260 | def test_get_lastfunction_header_should_return_header(self): 261 | """Test getting last specific header of the last API call works""" 262 | endpoint = 'statuses/home_timeline' 263 | url = self.get_url(endpoint) 264 | self.register_response(responses.GET, url, adding_headers={'x-rate-limit-remaining': '37'}) 265 | 266 | self.api.get(endpoint) 267 | 268 | value = self.api.get_lastfunction_header('x-rate-limit-remaining') 269 | self.assertEqual('37', value) 270 | value2 = self.api.get_lastfunction_header('does-not-exist') 271 | self.assertIsNone(value2) 272 | value3 = self.api.get_lastfunction_header('not-there-either', '96') 273 | self.assertEqual('96', value3) 274 | 275 | def test_get_lastfunction_header_should_raise_error_when_no_previous_call(self): 276 | """Test attempting to get a header when no API call was made raises a TwythonError""" 277 | self.assertRaises(TwythonError, self.api.get_lastfunction_header, 'no-api-call-was-made') 278 | 279 | @responses.activate 280 | def test_sends_correct_accept_encoding_header(self): 281 | """Test that Twython accepts compressed data.""" 282 | endpoint = 'statuses/home_timeline' 283 | url = self.get_url(endpoint) 284 | self.register_response(responses.GET, url) 285 | 286 | self.api.get(endpoint) 287 | 288 | self.assertEqual(b'gzip, deflate', responses.calls[0].request.headers['Accept-Encoding']) 289 | 290 | # Static methods 291 | def test_construct_api_url(self): 292 | """Test constructing a Twitter API url works as we expect""" 293 | url = 'https://api.twitter.com/1.1/search/tweets.json' 294 | constructed_url = self.api.construct_api_url(url, q='#twitter') 295 | self.assertEqual(constructed_url, 'https://api.twitter.com/1.1/search/tweets.json?q=%23twitter') 296 | 297 | def test_encode(self): 298 | """Test encoding UTF-8 works""" 299 | self.api.encode('Twython is awesome!') 300 | 301 | def test_cursor_requires_twython_function(self): 302 | """Test that cursor() raises when called without a Twython function""" 303 | def init_and_iterate_cursor(*args, **kwargs): 304 | cursor = self.api.cursor(*args, **kwargs) 305 | return next(cursor) 306 | 307 | non_function = object() 308 | non_twython_function = lambda x: x 309 | 310 | self.assertRaises(TypeError, init_and_iterate_cursor, non_function) 311 | self.assertRaises(TwythonError, init_and_iterate_cursor, non_twython_function) 312 | 313 | -------------------------------------------------------------------------------- /tests/test_html_for_tweet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | import os 4 | 5 | from twython import Twython, TwythonError 6 | 7 | from .config import unittest 8 | 9 | 10 | class TestHtmlForTweetTestCase(unittest.TestCase): 11 | def setUp(self): 12 | self.api = Twython('', '', '', '') 13 | 14 | def load_tweet(self, name): 15 | f = open(os.path.join( 16 | os.path.dirname(__file__), 17 | 'tweets', 18 | '%s.json' % name 19 | )) 20 | tweet = json.load(f) 21 | f.close() 22 | return tweet 23 | 24 | def test_basic(self): 25 | """Test HTML for Tweet returns what we want""" 26 | tweet_object = self.load_tweet('basic') 27 | tweet_text = self.api.html_for_tweet(tweet_object) 28 | self.assertEqual(tweet_text, 29 | 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71') 30 | 31 | def test_reply(self): 32 | """Test HTML for Tweet links the replied-to username.""" 33 | tweet_object = self.load_tweet('reply') 34 | tweet_text = self.api.html_for_tweet(tweet_object) 35 | self.assertEqual(tweet_text, 36 | u'@philgyford Here’s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr') 37 | 38 | def test_expanded_url(self): 39 | """Test using expanded url in HTML for Tweet displays full urls""" 40 | tweet_object = self.load_tweet('basic') 41 | tweet_text = self.api.html_for_tweet(tweet_object, 42 | use_expanded_url=True) 43 | # Make sure full url is in HTML 44 | self.assertTrue('http://google.com' in tweet_text) 45 | 46 | def test_short_url(self): 47 | """Test using expanded url in HTML for Tweet displays full urls""" 48 | tweet_object = self.load_tweet('basic') 49 | tweet_text = self.api.html_for_tweet(tweet_object, False) 50 | # Make sure HTML doesn't contain the display OR expanded url 51 | self.assertTrue('http://google.com' not in tweet_text) 52 | self.assertTrue('google.com' not in tweet_text) 53 | 54 | def test_identical_urls(self): 55 | """If the 'url's for different url entities are identical, they should link correctly.""" 56 | tweet_object = self.load_tweet('identical_urls') 57 | tweet_text = self.api.html_for_tweet(tweet_object) 58 | self.assertEqual(tweet_text, 59 | u'Use Cases, Trials and Making 5G a Reality buff.ly/2sEhrgO #5G #innovation via @5GWorldSeries buff.ly/2sEhrgO') 60 | 61 | def test_symbols(self): 62 | tweet_object = self.load_tweet('symbols') 63 | tweet_text = self.api.html_for_tweet(tweet_object) 64 | # Should only link symbols listed in entities: 65 | self.assertTrue('$AAPL' in tweet_text) 66 | self.assertTrue('$ANOTHER' not in tweet_text) 67 | 68 | def test_no_symbols(self): 69 | """Should still work if tweet object has no symbols list""" 70 | tweet = self.load_tweet('symbols') 71 | # Save a copy: 72 | symbols = tweet['entities']['symbols'] 73 | del tweet['entities']['symbols'] 74 | tweet_text = self.api.html_for_tweet(tweet) 75 | self.assertTrue('symbols: $AAPL and' in tweet_text) 76 | self.assertTrue('and $ANOTHER and $A.' in tweet_text) 77 | 78 | def test_compatmode(self): 79 | tweet_object = self.load_tweet('compat') 80 | tweet_text = self.api.html_for_tweet(tweet_object) 81 | # link to compat web status link 82 | self.assertTrue( 83 | u'twitter.com/i/web/status/7…' in tweet_text) 84 | 85 | def test_extendedmode(self): 86 | tweet_object = self.load_tweet('extended') 87 | tweet_text = self.api.html_for_tweet(tweet_object) 88 | # full tweet rendered with suffix 89 | self.assertEqual(tweet_text, 90 | 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC') 91 | 92 | def test_entities_with_prefix(self): 93 | """ 94 | If there is a username mention at the start of a tweet it's in the 95 | "prefix" and so isn't part of the main tweet display text. 96 | But its length is still counted in the indices of any subsequent 97 | mentions, urls, hashtags, etc. 98 | """ 99 | self.maxDiff = 2200 100 | tweet_object = self.load_tweet('entities_with_prefix') 101 | tweet_text = self.api.html_for_tweet(tweet_object) 102 | self.assertEqual(tweet_text, 103 | u'@philgyford This is a test for @visionphil that includes a link example.org and #hashtag and X for good measure AND that is longer than 140 characters. example.com') 104 | 105 | def test_media(self): 106 | tweet_object = self.load_tweet('media') 107 | tweet_text = self.api.html_for_tweet(tweet_object) 108 | 109 | self.assertEqual( 110 | u"""I made some D3.js charts showing the years covered by books in a series compared to their publishing dates gyford.com/phil/writing/2\u2026 pic.twitter.com/OwNc6uJklg""", 111 | tweet_text) 112 | 113 | def test_quoted(self): 114 | "With expand_quoted_status=True it should include a quoted tweet." 115 | tweet_object = self.load_tweet('quoted') 116 | tweet_text = self.api.html_for_tweet(tweet_object, 117 | expand_quoted_status=True) 118 | self.assertEqual( 119 | u"""Here\u2019s a quoted tweet. twitter.com/philgyford/sta\u2026
The quoted tweet text.Phil Gyford@philgyford
""", 120 | tweet_text) 121 | 122 | def test_retweet(self): 123 | "With expand_quoted_status=True it should include a quoted tweet." 124 | tweet_object = self.load_tweet('retweet') 125 | tweet_text = self.api.html_for_tweet(tweet_object) 126 | 127 | self.assertEqual( 128 | 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.""", 129 | tweet_text) 130 | 131 | -------------------------------------------------------------------------------- /tests/test_streaming.py: -------------------------------------------------------------------------------- 1 | from twython import TwythonStreamer, TwythonStreamError 2 | 3 | from .config import ( 4 | app_key, app_secret, oauth_token, oauth_token_secret, unittest 5 | ) 6 | 7 | 8 | class TwythonStreamTestCase(unittest.TestCase): 9 | def setUp(self): 10 | class MyStreamer(TwythonStreamer): 11 | def on_success(self, data): 12 | self.disconnect() 13 | 14 | def on_error(self, status_code, data): 15 | raise TwythonStreamError(data) 16 | 17 | self.api = MyStreamer(app_key, app_secret, 18 | oauth_token, oauth_token_secret) 19 | 20 | client_args = { 21 | 'headers': { 22 | 'User-Agent': '__twython__ Stream Test' 23 | } 24 | } 25 | # Initialize with header for coverage checking for User-Agent 26 | self.api_with_header = MyStreamer(app_key, app_secret, 27 | oauth_token, oauth_token_secret, 28 | client_args=client_args) 29 | 30 | @unittest.skip('skipping non-updated test') 31 | def test_stream_status_filter(self): 32 | self.api.statuses.filter(track='twitter') 33 | 34 | @unittest.skip('skipping non-updated test') 35 | def test_stream_status_sample(self): 36 | self.api.statuses.sample() 37 | 38 | @unittest.skip('skipping non-updated test') 39 | def test_stream_status_firehose(self): 40 | self.assertRaises(TwythonStreamError, self.api.statuses.firehose, 41 | track='twitter') 42 | 43 | @unittest.skip('skipping non-updated test') 44 | def test_stream_site(self): 45 | self.assertRaises(TwythonStreamError, self.api.site, 46 | follow='twitter') 47 | 48 | @unittest.skip('skipping non-updated test') 49 | def test_stream_user(self): 50 | self.api.user(track='twitter') 51 | -------------------------------------------------------------------------------- /tests/tweets/basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "contributors":null, 3 | "truncated":false, 4 | "text":"http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71", 5 | "in_reply_to_status_id":null, 6 | "id":349683012054683648, 7 | "favorite_count":0, 8 | "source":"Twitter Web Client", 9 | "retweeted":false, 10 | "coordinates":null, 11 | "entities":{ 12 | "symbols":[ 13 | 14 | ], 15 | "user_mentions":[ 16 | { 17 | "id":29251354, 18 | "indices":[ 19 | 45, 20 | 57 21 | ], 22 | "id_str":"29251354", 23 | "screen_name":"mikehelmick", 24 | "name":"Mike Helmick" 25 | }, 26 | { 27 | "id":1431865928, 28 | "indices":[ 29 | 81, 30 | 93 31 | ], 32 | "id_str":"1431865928", 33 | "screen_name":"__twython__", 34 | "name":"Twython" 35 | } 36 | ], 37 | "hashtags":[ 38 | { 39 | "indices":[ 40 | 28, 41 | 33 42 | ], 43 | "text":"cool" 44 | }, 45 | { 46 | "indices":[ 47 | 62, 48 | 73 49 | ], 50 | "text":"checkitout" 51 | } 52 | ], 53 | "urls":[ 54 | { 55 | "url":"http://t.co/FCmXyI6VHd", 56 | "indices":[ 57 | 0, 58 | 22 59 | ], 60 | "expanded_url":"http://google.com", 61 | "display_url":"google.com" 62 | }, 63 | { 64 | "url":"https://t.co/67pwRvY6z9", 65 | "indices":[ 66 | 94, 67 | 117 68 | ], 69 | "expanded_url":"https://github.com", 70 | "display_url":"github.com" 71 | } 72 | ], 73 | "media":[ 74 | { 75 | "id":537884378513162240, 76 | "id_str":"537884378513162240", 77 | "indices":[ 78 | 118, 79 | 140 80 | ], 81 | "media_url":"http://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg", 82 | "media_url_https":"https://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg", 83 | "url":"http://t.co/N6InAO4B71", 84 | "display_url":"pic.twitter.com/N6InAO4B71", 85 | "expanded_url":"http://twitter.com/pingofglitch/status/537884380060844032/photo/1", 86 | "type":"photo", 87 | "sizes":{ 88 | "large":{ 89 | "w":1024, 90 | "h":640, 91 | "resize":"fit" 92 | }, 93 | "thumb":{ 94 | "w":150, 95 | "h":150, 96 | "resize":"crop" 97 | }, 98 | "medium":{ 99 | "w":600, 100 | "h":375, 101 | "resize":"fit" 102 | }, 103 | "small":{ 104 | "w":340, 105 | "h":212, 106 | "resize":"fit" 107 | } 108 | } 109 | } 110 | ] 111 | }, 112 | "in_reply_to_screen_name":null, 113 | "id_str":"349683012054683648", 114 | "retweet_count":0, 115 | "in_reply_to_user_id":null, 116 | "favorited":false, 117 | "user":{ 118 | "follow_request_sent":false, 119 | "profile_use_background_image":true, 120 | "default_profile_image":true, 121 | "id":1431865928, 122 | "verified":false, 123 | "profile_text_color":"333333", 124 | "profile_image_url_https":"https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png", 125 | "profile_sidebar_fill_color":"DDEEF6", 126 | "entities":{ 127 | "description":{ 128 | "urls":[ 129 | 130 | ] 131 | } 132 | }, 133 | "followers_count":1, 134 | "profile_sidebar_border_color":"C0DEED", 135 | "id_str":"1431865928", 136 | "profile_background_color":"3D3D3D", 137 | "listed_count":0, 138 | "profile_background_image_url_https":"https://si0.twimg.com/images/themes/theme1/bg.png", 139 | "utc_offset":null, 140 | "statuses_count":2, 141 | "description":"", 142 | "friends_count":1, 143 | "location":"", 144 | "profile_link_color":"0084B4", 145 | "profile_image_url":"http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png", 146 | "following":false, 147 | "geo_enabled":false, 148 | "profile_background_image_url":"http://a0.twimg.com/images/themes/theme1/bg.png", 149 | "screen_name":"__twython__", 150 | "lang":"en", 151 | "profile_background_tile":false, 152 | "favourites_count":0, 153 | "name":"Twython", 154 | "notifications":false, 155 | "url":null, 156 | "created_at":"Thu May 16 01:11:09 +0000 2013", 157 | "contributors_enabled":false, 158 | "time_zone":null, 159 | "protected":false, 160 | "default_profile":false, 161 | "is_translator":false 162 | }, 163 | "geo":null, 164 | "in_reply_to_user_id_str":null, 165 | "possibly_sensitive":false, 166 | "lang":"en", 167 | "created_at":"Wed Jun 26 00:18:21 +0000 2013", 168 | "in_reply_to_status_id_str":null, 169 | "place":null 170 | } 171 | -------------------------------------------------------------------------------- /tests/tweets/compat.json: -------------------------------------------------------------------------------- 1 | { 2 | "contributors":null, 3 | "truncated":true, 4 | "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", 5 | "is_quote_status":false, 6 | "in_reply_to_status_id":null, 7 | "id":777915304261193728, 8 | "favorite_count":13856, 9 | "source":"Twitter Web Client", 10 | "retweeted":false, 11 | "coordinates":null, 12 | "entities":{ 13 | "symbols":[ 14 | 15 | ], 16 | "user_mentions":[ 17 | 18 | ], 19 | "hashtags":[ 20 | 21 | ], 22 | "urls":[ 23 | { 24 | "url":"https://t.co/SRmsuks2ru", 25 | "indices":[ 26 | 117, 27 | 140 28 | ], 29 | "expanded_url":"https://twitter.com/i/web/status/777915304261193728", 30 | "display_url":"twitter.com/i/web/status/7\u2026" 31 | } 32 | ] 33 | }, 34 | "in_reply_to_screen_name":null, 35 | "id_str":"777915304261193728", 36 | "retweet_count":14767, 37 | "in_reply_to_user_id":null, 38 | "favorited":false, 39 | "user":{ 40 | "name":"Twitter", 41 | "screen_name":"twitter" 42 | }, 43 | "geo":null, 44 | "in_reply_to_user_id_str":null, 45 | "possibly_sensitive":false, 46 | "possibly_sensitive_appealable":false, 47 | "lang":"en", 48 | "created_at":"Mon Sep 19 17:00:36 +0000 2016", 49 | "in_reply_to_status_id_str":null, 50 | "place":null 51 | } 52 | -------------------------------------------------------------------------------- /tests/tweets/entities_with_prefix.json: -------------------------------------------------------------------------------- 1 | { 2 | "created_at":"Sat Jan 06 18:56:35 +0000 2018", 3 | "id":949716340755091458, 4 | "id_str":"949716340755091458", 5 | "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", 6 | "truncated":false, 7 | "display_text_range":[ 12, 187 ], 8 | "entities":{ 9 | "hashtags":[ 10 | { 11 | "text":"hashtag", 12 | "indices":[ 92, 100 ] 13 | } 14 | ], 15 | "symbols":[ ], 16 | "user_mentions":[ 17 | { 18 | "screen_name":"philgyford", 19 | "name":"Phil Gyford", 20 | "id":12552, 21 | "id_str":"12552", 22 | "indices":[ 0, 11 ] 23 | }, 24 | { 25 | "screen_name":"visionphil", 26 | "name":"Vision Phil", 27 | "id":104456050, 28 | "id_str":"104456050", 29 | "indices":[ 31, 42 ] 30 | } 31 | ], 32 | "urls":[ 33 | { 34 | "url":"https://t.co/sKw4J3A8SZ", 35 | "expanded_url":"http://example.org", 36 | "display_url":"example.org", 37 | "indices":[ 64, 87 ] 38 | }, 39 | { 40 | "url":"https://t.co/jnQdy7Zg7u", 41 | "expanded_url":"http://example.com", 42 | "display_url":"example.com", 43 | "indices":[ 164, 187 ] 44 | } 45 | ] 46 | }, 47 | "source":"Tweetbot for Mac", 48 | "in_reply_to_status_id":948561036889722880, 49 | "in_reply_to_status_id_str":"948561036889722880", 50 | "in_reply_to_user_id":12552, 51 | "in_reply_to_user_id_str":"12552", 52 | "in_reply_to_screen_name":"philgyford", 53 | "user":{ 54 | "id":2030131, 55 | "id_str":"2030131" 56 | }, 57 | "geo":null, 58 | "coordinates":null, 59 | "place":null, 60 | "contributors":null, 61 | "is_quote_status":false, 62 | "retweet_count":0, 63 | "favorite_count":0, 64 | "favorited":false, 65 | "retweeted":false, 66 | "possibly_sensitive":false, 67 | "lang":"en" 68 | } 69 | -------------------------------------------------------------------------------- /tests/tweets/extended.json: -------------------------------------------------------------------------------- 1 | { 2 | "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", 3 | "truncated":false, 4 | "is_quote_status":false, 5 | "in_reply_to_status_id":null, 6 | "id":777915304261193728, 7 | "favorite_count":13856, 8 | "contributors":null, 9 | "source":"Twitter Web Client", 10 | "retweeted":false, 11 | "coordinates":null, 12 | "entities":{ 13 | "symbols":[ 14 | 15 | ], 16 | "user_mentions":[ 17 | 18 | ], 19 | "hashtags":[ 20 | 21 | ], 22 | "urls":[ 23 | 24 | ], 25 | "media":[ 26 | { 27 | "expanded_url":"https://twitter.com/twitter/status/777915304261193728/photo/1", 28 | "sizes":{ 29 | "small":{ 30 | "h":340, 31 | "w":340, 32 | "resize":"fit" 33 | }, 34 | "large":{ 35 | "h":700, 36 | "w":700, 37 | "resize":"fit" 38 | }, 39 | "medium":{ 40 | "h":600, 41 | "w":600, 42 | "resize":"fit" 43 | }, 44 | "thumb":{ 45 | "h":150, 46 | "w":150, 47 | "resize":"crop" 48 | } 49 | }, 50 | "url":"https://t.co/I9pUC0NdZC", 51 | "media_url_https":"https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg", 52 | "id_str":"777914712382058496", 53 | "indices":[ 54 | 140, 55 | 163 56 | ], 57 | "media_url":"http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg", 58 | "type":"photo", 59 | "id":777914712382058496, 60 | "display_url":"pic.twitter.com/I9pUC0NdZC" 61 | } 62 | ] 63 | }, 64 | "in_reply_to_screen_name":null, 65 | "id_str":"777915304261193728", 66 | "display_text_range":[ 67 | 0, 68 | 139 69 | ], 70 | "retweet_count":14767, 71 | "in_reply_to_user_id":null, 72 | "favorited":false, 73 | "user":{ 74 | "follow_request_sent":false, 75 | "has_extended_profile":false, 76 | "profile_use_background_image":true, 77 | "id":783214, 78 | "verified":true, 79 | "profile_text_color":"333333", 80 | "profile_image_url_https":"https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg", 81 | "profile_sidebar_fill_color":"F6F6F6", 82 | "is_translator":false, 83 | "geo_enabled":true, 84 | "entities":{ 85 | "url":{ 86 | "urls":[ 87 | { 88 | "url":"http://t.co/5iRhy7wTgu", 89 | "indices":[ 90 | 0, 91 | 22 92 | ], 93 | "expanded_url":"http://blog.twitter.com/", 94 | "display_url":"blog.twitter.com" 95 | } 96 | ] 97 | }, 98 | "description":{ 99 | "urls":[ 100 | { 101 | "url":"https://t.co/qq1HEzvnrA", 102 | "indices":[ 103 | 84, 104 | 107 105 | ], 106 | "expanded_url":"http://support.twitter.com", 107 | "display_url":"support.twitter.com" 108 | } 109 | ] 110 | } 111 | }, 112 | "followers_count":56827498, 113 | "protected":false, 114 | "location":"San Francisco, CA", 115 | "default_profile_image":false, 116 | "id_str":"783214", 117 | "lang":"en", 118 | "utc_offset":-25200, 119 | "statuses_count":3161, 120 | "description":"Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.", 121 | "friends_count":145, 122 | "profile_link_color":"226699", 123 | "profile_image_url":"http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg", 124 | "notifications":false, 125 | "profile_background_image_url_https":"https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png", 126 | "profile_background_color":"ACDED6", 127 | "profile_banner_url":"https://pbs.twimg.com/profile_banners/783214/1471929200", 128 | "profile_background_image_url":"http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png", 129 | "name":"Twitter", 130 | "is_translation_enabled":false, 131 | "profile_background_tile":true, 132 | "favourites_count":2332, 133 | "screen_name":"twitter", 134 | "url":"http://t.co/5iRhy7wTgu", 135 | "created_at":"Tue Feb 20 14:35:54 +0000 2007", 136 | "contributors_enabled":false, 137 | "time_zone":"Pacific Time (US & Canada)", 138 | "profile_sidebar_border_color":"FFFFFF", 139 | "default_profile":false, 140 | "following":false, 141 | "listed_count":90445 142 | }, 143 | "geo":null, 144 | "in_reply_to_user_id_str":null, 145 | "possibly_sensitive":false, 146 | "possibly_sensitive_appealable":false, 147 | "lang":"en", 148 | "created_at":"Mon Sep 19 17:00:36 +0000 2016", 149 | "in_reply_to_status_id_str":null, 150 | "place":null, 151 | "extended_entities":{ 152 | "media":[ 153 | { 154 | "expanded_url":"https://twitter.com/twitter/status/777915304261193728/photo/1", 155 | "display_url":"pic.twitter.com/I9pUC0NdZC", 156 | "url":"https://t.co/I9pUC0NdZC", 157 | "media_url_https":"https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg", 158 | "video_info":{ 159 | "aspect_ratio":[ 160 | 1, 161 | 1 162 | ], 163 | "variants":[ 164 | { 165 | "url":"https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4", 166 | "bitrate":0, 167 | "content_type":"video/mp4" 168 | } 169 | ] 170 | }, 171 | "id_str":"777914712382058496", 172 | "sizes":{ 173 | "small":{ 174 | "h":340, 175 | "w":340, 176 | "resize":"fit" 177 | }, 178 | "large":{ 179 | "h":700, 180 | "w":700, 181 | "resize":"fit" 182 | }, 183 | "medium":{ 184 | "h":600, 185 | "w":600, 186 | "resize":"fit" 187 | }, 188 | "thumb":{ 189 | "h":150, 190 | "w":150, 191 | "resize":"crop" 192 | } 193 | }, 194 | "indices":[ 195 | 140, 196 | 163 197 | ], 198 | "type":"animated_gif", 199 | "id":777914712382058496, 200 | "media_url":"http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg" 201 | } 202 | ] 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /tests/tweets/identical_urls.json: -------------------------------------------------------------------------------- 1 | { 2 | "entities":{ 3 | "hashtags":[ 4 | 5 | ], 6 | "user_mentions":[ 7 | 8 | ], 9 | "symbols":[ 10 | 11 | ], 12 | "urls":[ 13 | { 14 | "display_url":"buff.ly/2sEhrgO", 15 | "expanded_url":"http://buff.ly/2sEhrgO", 16 | "indices":[ 17 | 42, 18 | 65 19 | ], 20 | "url":"https://t.co/W0uArTMk9N" 21 | }, 22 | { 23 | "display_url":"buff.ly/2sEhrgO", 24 | "expanded_url":"http://buff.ly/2sEhrgO", 25 | "indices":[ 26 | 101, 27 | 124 28 | ], 29 | "url":"https://t.co/W0uArTMk9N" 30 | } 31 | ] 32 | }, 33 | "full_text":"Use Cases, Trials and Making 5G a Reality https://t.co/W0uArTMk9N #5G #innovation via @5GWorldSeries https://t.co/W0uArTMk9N" 34 | } 35 | -------------------------------------------------------------------------------- /tests/tweets/media.json: -------------------------------------------------------------------------------- 1 | { 2 | "contributors":null, 3 | "truncated":false, 4 | "is_quote_status":false, 5 | "in_reply_to_status_id":null, 6 | "id":905105588279013377, 7 | "favorite_count":10, 8 | "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", 9 | "source":"Twitter Web Client", 10 | "retweeted":false, 11 | "coordinates":null, 12 | "entities":{ 13 | "symbols":[ 14 | 15 | ], 16 | "user_mentions":[ 17 | 18 | ], 19 | "hashtags":[ 20 | 21 | ], 22 | "urls":[ 23 | { 24 | "url":"https://t.co/2yUmmn3TOc", 25 | "indices":[ 26 | 107, 27 | 130 28 | ], 29 | "expanded_url":"http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php", 30 | "display_url":"gyford.com/phil/writing/2\u2026" 31 | } 32 | ], 33 | "media":[ 34 | { 35 | "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", 36 | "display_url":"pic.twitter.com/OwNc6uJklg", 37 | "url":"https://t.co/OwNc6uJklg", 38 | "media_url_https":"https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", 39 | "id_str":"905105571765944320", 40 | "sizes":{ 41 | "small":{ 42 | "h":256, 43 | "resize":"fit", 44 | "w":680 45 | }, 46 | "large":{ 47 | "h":376, 48 | "resize":"fit", 49 | "w":1000 50 | }, 51 | "medium":{ 52 | "h":376, 53 | "resize":"fit", 54 | "w":1000 55 | }, 56 | "thumb":{ 57 | "h":150, 58 | "resize":"crop", 59 | "w":150 60 | } 61 | }, 62 | "indices":[ 63 | 131, 64 | 154 65 | ], 66 | "type":"photo", 67 | "id":905105571765944320, 68 | "media_url":"http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg" 69 | } 70 | ] 71 | }, 72 | "in_reply_to_screen_name":null, 73 | "in_reply_to_user_id":null, 74 | "display_text_range":[ 75 | 0, 76 | 130 77 | ], 78 | "retweet_count":1, 79 | "id_str":"905105588279013377", 80 | "favorited":false, 81 | "user":{ 82 | "screen_name":"philgyford", 83 | "name":"Phil Gyford" 84 | }, 85 | "geo":null, 86 | "in_reply_to_user_id_str":null, 87 | "possibly_sensitive":false, 88 | "possibly_sensitive_appealable":false, 89 | "lang":"en", 90 | "created_at":"Tue Sep 05 16:29:22 +0000 2017", 91 | "in_reply_to_status_id_str":null, 92 | "place":null, 93 | "extended_entities":{ 94 | "media":[ 95 | { 96 | "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", 97 | "display_url":"pic.twitter.com/OwNc6uJklg", 98 | "url":"https://t.co/OwNc6uJklg", 99 | "media_url_https":"https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", 100 | "id_str":"905105571765944320", 101 | "sizes":{ 102 | "small":{ 103 | "h":256, 104 | "resize":"fit", 105 | "w":680 106 | }, 107 | "large":{ 108 | "h":376, 109 | "resize":"fit", 110 | "w":1000 111 | }, 112 | "medium":{ 113 | "h":376, 114 | "resize":"fit", 115 | "w":1000 116 | }, 117 | "thumb":{ 118 | "h":150, 119 | "resize":"crop", 120 | "w":150 121 | } 122 | }, 123 | "indices":[ 124 | 131, 125 | 154 126 | ], 127 | "type":"photo", 128 | "id":905105571765944320, 129 | "media_url":"http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg" 130 | }, 131 | { 132 | "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", 133 | "display_url":"pic.twitter.com/OwNc6uJklg", 134 | "url":"https://t.co/OwNc6uJklg", 135 | "media_url_https":"https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg", 136 | "id_str":"905105572529393668", 137 | "sizes":{ 138 | "large":{ 139 | "h":399, 140 | "resize":"fit", 141 | "w":1000 142 | }, 143 | "small":{ 144 | "h":271, 145 | "resize":"fit", 146 | "w":680 147 | }, 148 | "medium":{ 149 | "h":399, 150 | "resize":"fit", 151 | "w":1000 152 | }, 153 | "thumb":{ 154 | "h":150, 155 | "resize":"crop", 156 | "w":150 157 | } 158 | }, 159 | "indices":[ 160 | 131, 161 | 154 162 | ], 163 | "type":"photo", 164 | "id":905105572529393668, 165 | "media_url":"http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg" 166 | }, 167 | { 168 | "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", 169 | "display_url":"pic.twitter.com/OwNc6uJklg", 170 | "url":"https://t.co/OwNc6uJklg", 171 | "media_url_https":"https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg", 172 | "id_str":"905105573255016448", 173 | "sizes":{ 174 | "small":{ 175 | "h":195, 176 | "resize":"fit", 177 | "w":680 178 | }, 179 | "large":{ 180 | "h":287, 181 | "resize":"fit", 182 | "w":1000 183 | }, 184 | "medium":{ 185 | "h":287, 186 | "resize":"fit", 187 | "w":1000 188 | }, 189 | "thumb":{ 190 | "h":150, 191 | "resize":"crop", 192 | "w":150 193 | } 194 | }, 195 | "indices":[ 196 | 131, 197 | 154 198 | ], 199 | "type":"photo", 200 | "id":905105573255016448, 201 | "media_url":"http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg" 202 | } 203 | ] 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /tests/tweets/quoted.json: -------------------------------------------------------------------------------- 1 | { 2 | "contributors":null, 3 | "truncated":false, 4 | "text":"Here\u2019s a quoted tweet. https://t.co/3neKzof0gT", 5 | "is_quote_status":true, 6 | "in_reply_to_status_id":null, 7 | "id":917706313785905157, 8 | "favorite_count":0, 9 | "source":"Twitter Web Client", 10 | "quoted_status_id":917699069916729344, 11 | "retweeted":false, 12 | "coordinates":null, 13 | "quoted_status":{ 14 | "contributors":null, 15 | "truncated":false, 16 | "text":"The quoted tweet text.", 17 | "is_quote_status":false, 18 | "in_reply_to_status_id":null, 19 | "id":917699069916729344, 20 | "favorite_count":1, 21 | "source":"Twitter Web Client", 22 | "retweeted":false, 23 | "coordinates":null, 24 | "entities":{ 25 | "symbols":[ 26 | 27 | ], 28 | "user_mentions":[ 29 | 30 | ], 31 | "hashtags":[ 32 | 33 | ], 34 | "urls":[ 35 | 36 | ] 37 | }, 38 | "in_reply_to_screen_name":null, 39 | "in_reply_to_user_id":null, 40 | "retweet_count":0, 41 | "id_str":"917699069916729344", 42 | "favorited":false, 43 | "user":{ 44 | "screen_name":"philgyford", 45 | "name":"Phil Gyford" 46 | }, 47 | "geo":null, 48 | "in_reply_to_user_id_str":null, 49 | "lang":"ht", 50 | "created_at":"Tue Oct 10 10:31:22 +0000 2017", 51 | "in_reply_to_status_id_str":null, 52 | "place":null 53 | }, 54 | "entities":{ 55 | "symbols":[ 56 | 57 | ], 58 | "user_mentions":[ 59 | 60 | ], 61 | "hashtags":[ 62 | 63 | ], 64 | "urls":[ 65 | { 66 | "url":"https://t.co/3neKzof0gT", 67 | "indices":[ 68 | 23, 69 | 46 70 | ], 71 | "expanded_url":"https://twitter.com/philgyford/status/917699069916729344", 72 | "display_url":"twitter.com/philgyford/sta\u2026" 73 | } 74 | ] 75 | }, 76 | "in_reply_to_screen_name":null, 77 | "in_reply_to_user_id":null, 78 | "retweet_count":0, 79 | "id_str":"917706313785905157", 80 | "favorited":false, 81 | "user":{ 82 | "screen_name":"philgyfordtest", 83 | "name":"Phil Gyford Test" 84 | }, 85 | "geo":null, 86 | "in_reply_to_user_id_str":null, 87 | "possibly_sensitive":false, 88 | "possibly_sensitive_appealable":false, 89 | "lang":"en", 90 | "created_at":"Tue Oct 10 11:00:10 +0000 2017", 91 | "quoted_status_id_str":"917699069916729344", 92 | "in_reply_to_status_id_str":null, 93 | "place":null 94 | } 95 | -------------------------------------------------------------------------------- /tests/tweets/reply.json: -------------------------------------------------------------------------------- 1 | { 2 | "display_text_range":[ 3 | 12, 4 | 114 5 | ], 6 | "in_reply_to_status_id_str":"742374355531923456", 7 | "source":"Twitter Web Client", 8 | "geo":null, 9 | "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", 10 | "extended_entities":{ 11 | "media":[ 12 | 13 | ] 14 | }, 15 | "id_str":"300", 16 | "in_reply_to_status_id":742374355531923456, 17 | "id":300, 18 | "in_reply_to_screen_name":"philgyford", 19 | "retweet_count":0, 20 | "user":{ 21 | 22 | }, 23 | "created_at":"Mon Jun 13 15:48:06 +0000 2016", 24 | "lang":"en", 25 | "favorite_count":0, 26 | "coordinates":null, 27 | "place":null, 28 | "contributors":null, 29 | "in_reply_to_user_id":12552, 30 | "in_reply_to_user_id_str":"12552", 31 | "retweeted":false, 32 | "favorited":false, 33 | "truncated":false, 34 | "entities":{ 35 | "user_mentions":[ 36 | { 37 | "id_str":"12552", 38 | "id":12552, 39 | "screen_name":"philgyford", 40 | "name":"Phil Gyford", 41 | "indices":[ 42 | 0, 43 | 11 44 | ] 45 | } 46 | ], 47 | "media":[ 48 | 49 | ], 50 | "hashtags":[ 51 | 52 | ], 53 | "symbols":[ 54 | 55 | ], 56 | "urls":[ 57 | 58 | ] 59 | }, 60 | "is_quote_status":false, 61 | "possibly_sensitive":false 62 | } 63 | -------------------------------------------------------------------------------- /tests/tweets/retweet.json: -------------------------------------------------------------------------------- 1 | { 2 | "coordinates":null, 3 | "source":"web", 4 | "in_reply_to_user_id_str":null, 5 | "truncated":false, 6 | "in_reply_to_user_id":null, 7 | "favorite_count":0, 8 | "user":{ 9 | "name":"Phil Gyford Test", 10 | "screen_name":"philgyfordtest" 11 | }, 12 | "favorited":false, 13 | "retweeted_status":{ 14 | "coordinates":null, 15 | "source":"Twitter Web Client", 16 | "in_reply_to_user_id_str":null, 17 | "truncated":false, 18 | "in_reply_to_user_id":null, 19 | "favorite_count":21, 20 | "user":{ 21 | "name":"Samuel Pepys", 22 | "screen_name":"samuelpepys" 23 | }, 24 | "favorited":false, 25 | "id":917459832885653506, 26 | "contributors":null, 27 | "in_reply_to_screen_name":null, 28 | "geo":null, 29 | "in_reply_to_status_id_str":null, 30 | "id_str":"917459832885653506", 31 | "entities":{ 32 | "hashtags":[ 33 | 34 | ], 35 | "symbols":[ 36 | 37 | ], 38 | "user_mentions":[ 39 | 40 | ], 41 | "urls":[ 42 | 43 | ] 44 | }, 45 | "in_reply_to_status_id":null, 46 | "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.", 47 | "retweet_count":3, 48 | "place":null, 49 | "lang":"en", 50 | "created_at":"Mon Oct 09 18:40:44 +0000 2017", 51 | "is_quote_status":false, 52 | "retweeted":false 53 | }, 54 | "id":917712989649801216, 55 | "contributors":null, 56 | "in_reply_to_screen_name":null, 57 | "geo":null, 58 | "in_reply_to_status_id_str":null, 59 | "id_str":"917712989649801216", 60 | "entities":{ 61 | "hashtags":[ 62 | 63 | ], 64 | "symbols":[ 65 | 66 | ], 67 | "user_mentions":[ 68 | { 69 | "id_str":"14475268", 70 | "indices":[ 71 | 3, 72 | 15 73 | ], 74 | "name":"Samuel Pepys", 75 | "id":14475268, 76 | "screen_name":"samuelpepys" 77 | } 78 | ], 79 | "urls":[ 80 | 81 | ] 82 | }, 83 | "in_reply_to_status_id":null, 84 | "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.", 85 | "retweet_count":3, 86 | "place":null, 87 | "lang":"en", 88 | "created_at":"Tue Oct 10 11:26:41 +0000 2017", 89 | "is_quote_status":false, 90 | "retweeted":false 91 | } 92 | -------------------------------------------------------------------------------- /tests/tweets/symbols.json: -------------------------------------------------------------------------------- 1 | { 2 | "text":"Some symbols: $AAPL and $PEP and $ANOTHER and $A.", 3 | "contributors":null, 4 | "geo":null, 5 | "favorited":true, 6 | "in_reply_to_user_id_str":null, 7 | "user":{ 8 | "screen_name":"philgyfordtest", 9 | "name":"Phil Gyford Test" 10 | }, 11 | "in_reply_to_user_id":null, 12 | "retweeted":false, 13 | "coordinates":null, 14 | "place":null, 15 | "in_reply_to_status_id":null, 16 | "lang":"en", 17 | "in_reply_to_status_id_str":null, 18 | "truncated":false, 19 | "retweet_count":0, 20 | "is_quote_status":false, 21 | "id":662694880657989632, 22 | "id_str":"662694880657989632", 23 | "in_reply_to_screen_name":null, 24 | "favorite_count":1, 25 | "entities":{ 26 | "hashtags":[ 27 | 28 | ], 29 | "user_mentions":[ 30 | 31 | ], 32 | "symbols":[ 33 | { 34 | "indices":[ 35 | 14, 36 | 19 37 | ], 38 | "text":"AAPL" 39 | }, 40 | { 41 | "indices":[ 42 | 24, 43 | 28 44 | ], 45 | "text":"PEP" 46 | }, 47 | { 48 | "indices":[ 49 | 46, 50 | 48 51 | ], 52 | "text":"A" 53 | } 54 | ], 55 | "urls":[ 56 | 57 | ] 58 | }, 59 | "created_at":"Fri Nov 06 18:15:46 +0000 2015", 60 | "source":"Twitter Web Client" 61 | } 62 | -------------------------------------------------------------------------------- /twython/__init__.py: -------------------------------------------------------------------------------- 1 | # ______ __ __ 2 | # /_ __/_ __ __ __ / /_ / /_ ____ ____ 3 | # / / | | /| / // / / // __// __ \ / __ \ / __ \ 4 | # / / | |/ |/ // /_/ // /_ / / / // /_/ // / / / 5 | # /_/ |__/|__/ \__, / \__//_/ /_/ \____//_/ /_/ 6 | # /____/ 7 | 8 | """ 9 | Twython 10 | ------- 11 | 12 | Twython is a library for Python that wraps the Twitter API. 13 | 14 | It aims to abstract away all the API endpoints, so that 15 | additions to the library and/or the Twitter API won't 16 | cause any overall problems. 17 | 18 | Questions, comments? ryan@venodesigns.net 19 | """ 20 | 21 | __author__ = 'Ryan McGrath ' 22 | __version__ = '3.9.1' 23 | 24 | from .api import Twython 25 | from .streaming import TwythonStreamer 26 | from .exceptions import ( 27 | TwythonError, TwythonRateLimitError, TwythonAuthError, 28 | TwythonStreamError 29 | ) 30 | -------------------------------------------------------------------------------- /twython/advisory.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | twython.advisory 5 | ~~~~~~~~~~~~~~~~ 6 | 7 | This module contains Warning classes for Twython to specifically 8 | alert the user about. 9 | 10 | This mainly is because Python 2.7 > mutes DeprecationWarning and when 11 | we deprecate a method or variable in Twython, we want to use to see 12 | the Warning but don't want ALL DeprecationWarnings to appear, 13 | only TwythonDeprecationWarnings. 14 | """ 15 | 16 | 17 | class TwythonDeprecationWarning(DeprecationWarning): 18 | """Custom DeprecationWarning to be raised when methods/variables 19 | are being deprecated in Twython. Python 2.7 > ignores DeprecationWarning 20 | so we want to specifically bubble up ONLY Twython Deprecation Warnings 21 | """ 22 | pass 23 | -------------------------------------------------------------------------------- /twython/compat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | twython.compat 5 | ~~~~~~~~~~~~~~ 6 | 7 | This module contains imports and declarations for seamless Python 2 and 8 | Python 3 compatibility. 9 | """ 10 | 11 | import sys 12 | 13 | _ver = sys.version_info 14 | 15 | #: Python 2.x? 16 | is_py2 = (_ver[0] == 2) 17 | 18 | #: Python 3.x? 19 | is_py3 = (_ver[0] == 3) 20 | 21 | try: 22 | import simplejson as json 23 | except ImportError: 24 | import json 25 | 26 | if is_py2: 27 | from urllib import urlencode, quote_plus 28 | from urlparse import parse_qsl, urlsplit 29 | 30 | str = unicode 31 | basestring = basestring 32 | numeric_types = (int, long, float) 33 | 34 | 35 | elif is_py3: 36 | from urllib.parse import urlencode, quote_plus, parse_qsl, urlsplit 37 | 38 | str = str 39 | basestring = (str, bytes) 40 | numeric_types = (int, float) 41 | -------------------------------------------------------------------------------- /twython/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | twython.exceptions 5 | ~~~~~~~~~~~~~~~~~~ 6 | 7 | This module contains Twython specific Exception classes. 8 | """ 9 | 10 | from .endpoints import TWITTER_HTTP_STATUS_CODE 11 | 12 | 13 | class TwythonError(Exception): 14 | """Generic error class, catch-all for most Twython issues. 15 | Special cases are handled by TwythonAuthError & TwythonRateLimitError. 16 | 17 | from twython import TwythonError, TwythonRateLimitError, TwythonAuthError 18 | 19 | """ 20 | def __init__(self, msg, error_code=None, retry_after=None): 21 | self.error_code = error_code 22 | 23 | if error_code is not None and error_code in TWITTER_HTTP_STATUS_CODE: 24 | msg = 'Twitter API returned a %s (%s), %s' % \ 25 | (error_code, 26 | TWITTER_HTTP_STATUS_CODE[error_code][0], 27 | msg) 28 | 29 | super(TwythonError, self).__init__(msg) 30 | 31 | @property 32 | def msg(self): # pragma: no cover 33 | return self.args[0] 34 | 35 | 36 | class TwythonAuthError(TwythonError): 37 | """Raised when you try to access a protected resource and it fails due to 38 | some issue with your authentication. 39 | 40 | """ 41 | pass 42 | 43 | 44 | class TwythonRateLimitError(TwythonError): # pragma: no cover 45 | """Raised when you've hit a rate limit. 46 | 47 | The amount of seconds to retry your request in will be appended 48 | to the message. 49 | 50 | """ 51 | def __init__(self, msg, error_code, retry_after=None): 52 | if isinstance(retry_after, int): 53 | msg = '%s (Retry after %d seconds)' % (msg, retry_after) 54 | TwythonError.__init__(self, msg, error_code=error_code) 55 | 56 | self.retry_after = retry_after 57 | 58 | 59 | class TwythonStreamError(TwythonError): 60 | """Raised when an invalid response from the Stream API is received""" 61 | pass 62 | -------------------------------------------------------------------------------- /twython/helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | twython.helpers 5 | ~~~~~~~~~~~~~~~ 6 | 7 | This module contains functions that are repeatedly used throughout 8 | the Twython library. 9 | """ 10 | 11 | from .compat import basestring, numeric_types 12 | 13 | 14 | def _transparent_params(_params): 15 | params = {} 16 | files = {} 17 | for k, v in _params.items(): 18 | if hasattr(v, 'read') and callable(v.read): 19 | files[k] = v # pragma: no cover 20 | elif isinstance(v, bool): 21 | if v: 22 | params[k] = 'true' 23 | else: 24 | params[k] = 'false' 25 | elif isinstance(v, basestring) or isinstance(v, numeric_types): 26 | params[k] = v 27 | elif isinstance(v, list): 28 | try: 29 | params[k] = ','.join(v) 30 | except TypeError: 31 | params[k] = ','.join(map(str, v)) 32 | else: 33 | continue # pragma: no cover 34 | return params, files 35 | -------------------------------------------------------------------------------- /twython/streaming/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import TwythonStreamer 2 | -------------------------------------------------------------------------------- /twython/streaming/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | twython.streaming.api 5 | ~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | This module contains functionality for interfacing with streaming 8 | Twitter API calls. 9 | """ 10 | 11 | from .. import __version__ 12 | from ..compat import json, is_py3 13 | from ..helpers import _transparent_params 14 | from .types import TwythonStreamerTypes 15 | 16 | import requests 17 | from requests_oauthlib import OAuth1 18 | 19 | import time 20 | 21 | 22 | class TwythonStreamer(object): 23 | def __init__(self, app_key, app_secret, oauth_token, oauth_token_secret, 24 | timeout=300, retry_count=None, retry_in=10, client_args=None, 25 | handlers=None, chunk_size=1): 26 | """Streaming class for a friendly streaming user experience 27 | Authentication IS required to use the Twitter Streaming API 28 | 29 | :param app_key: (required) Your applications key 30 | :param app_secret: (required) Your applications secret key 31 | :param oauth_token: (required) Used with oauth_token_secret to make 32 | authenticated calls 33 | :param oauth_token_secret: (required) Used with oauth_token to make 34 | authenticated calls 35 | :param timeout: (optional) How long (in secs) the streamer should wait 36 | for a response from Twitter Streaming API 37 | :param retry_count: (optional) Number of times the API call should be 38 | retired 39 | :param retry_in: (optional) Amount of time (in secs) the previous 40 | API call should be tried again 41 | :param client_args: (optional) Accepts some requests Session 42 | parameters and some requests Request parameters. 43 | See 44 | http://docs.python-requests.org/en/latest/api/#sessionapi 45 | and requests section below it for details. 46 | [ex. headers, proxies, verify(SSL verification)] 47 | :param handlers: (optional) Array of message types for which 48 | corresponding handlers will be called 49 | 50 | :param chunk_size: (optional) Define the buffer size before data is 51 | actually returned from the Streaming API. Default: 1 52 | """ 53 | 54 | self.auth = OAuth1(app_key, app_secret, 55 | oauth_token, oauth_token_secret) 56 | 57 | self.client_args = client_args or {} 58 | default_headers = {'User-Agent': 'Twython Streaming v' + __version__} 59 | if 'headers' not in self.client_args: 60 | # If they didn't set any headers, set our defaults for them 61 | self.client_args['headers'] = default_headers 62 | elif 'User-Agent' not in self.client_args['headers']: 63 | # If they set headers, but didn't include User-Agent.. 64 | # set it for them 65 | self.client_args['headers'].update(default_headers) 66 | self.client_args['timeout'] = timeout 67 | 68 | self.client = requests.Session() 69 | self.client.auth = self.auth 70 | self.client.stream = True 71 | 72 | # Make a copy of the client args and iterate over them 73 | # Pop out all the acceptable args at this point because they will 74 | # Never be used again. 75 | client_args_copy = self.client_args.copy() 76 | for k, v in client_args_copy.items(): 77 | if k in ('cert', 'headers', 'hooks', 'max_redirects', 'proxies'): 78 | setattr(self.client, k, v) 79 | self.client_args.pop(k) # Pop, pop! 80 | 81 | self.api_version = '1.1' 82 | 83 | self.retry_in = retry_in 84 | self.retry_count = retry_count 85 | 86 | # Set up type methods 87 | StreamTypes = TwythonStreamerTypes(self) 88 | self.statuses = StreamTypes.statuses 89 | 90 | self.connected = False 91 | 92 | self.handlers = handlers if handlers else \ 93 | ['delete', 'limit', 'disconnect'] 94 | 95 | self.chunk_size = chunk_size 96 | 97 | def _request(self, url, method='GET', params=None): 98 | """Internal stream request handling""" 99 | self.connected = True 100 | retry_counter = 0 101 | 102 | method = method.lower() 103 | func = getattr(self.client, method) 104 | params, _ = _transparent_params(params) 105 | 106 | def _send(retry_counter): 107 | requests_args = {} 108 | for k, v in self.client_args.items(): 109 | # Maybe this should be set as a class 110 | # variable and only done once? 111 | if k in ('timeout', 'allow_redirects', 'verify'): 112 | requests_args[k] = v 113 | 114 | while self.connected: 115 | try: 116 | if method == 'get': 117 | requests_args['params'] = params 118 | else: 119 | requests_args['data'] = params 120 | 121 | response = func(url, **requests_args) 122 | except requests.exceptions.Timeout: 123 | self.on_timeout() 124 | else: 125 | if response.status_code != 200: 126 | self.on_error(response.status_code, response.content, response.headers) 127 | 128 | if self.retry_count and \ 129 | (self.retry_count - retry_counter) > 0: 130 | time.sleep(self.retry_in) 131 | retry_counter += 1 132 | _send(retry_counter) 133 | 134 | return response 135 | 136 | while self.connected: 137 | response = _send(retry_counter) 138 | 139 | for line in response.iter_lines(self.chunk_size): 140 | if not self.connected: 141 | break 142 | if line: 143 | try: 144 | if is_py3: 145 | line = line.decode('utf-8') 146 | data = json.loads(line) 147 | except ValueError: # pragma: no cover 148 | self.on_error(response.status_code, 149 | 'Unable to decode response, \ 150 | not valid JSON.') 151 | else: 152 | if self.on_success(data): # pragma: no cover 153 | for message_type in self.handlers: 154 | if message_type in data: 155 | handler = getattr(self, 156 | 'on_' + message_type, 157 | None) 158 | if handler \ 159 | and callable(handler) \ 160 | and not handler(data.get(message_type)): 161 | break 162 | 163 | response.close() 164 | 165 | def on_success(self, data): # pragma: no cover 166 | """Called when data has been successfully received from the stream. 167 | Returns True if other handlers for this message should be invoked. 168 | 169 | Feel free to override this to handle your streaming data how you 170 | want it handled. See 171 | https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/streaming-message-types 172 | for messages sent along in stream responses. 173 | 174 | :param data: data recieved from the stream 175 | :type data: dict 176 | """ 177 | return True 178 | 179 | def on_error(self, status_code, data, headers=None): # pragma: no cover 180 | """Called when stream returns non-200 status code 181 | 182 | Feel free to override this to handle your streaming data how you 183 | want it handled. 184 | 185 | :param status_code: Non-200 status code sent from stream 186 | :type status_code: int 187 | 188 | :param data: Error message sent from stream 189 | :type data: dict 190 | 191 | :param headers: Response headers sent from the stream (i.e. Retry-After) 192 | :type headers: dict 193 | """ 194 | return 195 | 196 | def on_timeout(self): # pragma: no cover 197 | """ Called when the request has timed out """ 198 | return 199 | 200 | def disconnect(self): 201 | """Used to disconnect the streaming client manually""" 202 | self.connected = False 203 | -------------------------------------------------------------------------------- /twython/streaming/types.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | twython.streaming.types 5 | ~~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | This module contains classes and methods for :class:`TwythonStreamer` to use. 8 | """ 9 | 10 | 11 | class TwythonStreamerTypes(object): 12 | """Class for different stream endpoints 13 | 14 | Not all streaming endpoints have nested endpoints. 15 | User Streams and Site Streams are single streams with no nested endpoints 16 | Status Streams include filter, sample and firehose endpoints 17 | 18 | """ 19 | def __init__(self, streamer): 20 | self.streamer = streamer 21 | self.statuses = TwythonStreamerTypesStatuses(streamer) 22 | 23 | 24 | class TwythonStreamerTypesStatuses(object): 25 | """Class for different statuses endpoints 26 | 27 | Available so :meth:`TwythonStreamer.statuses.filter()` is available. 28 | Just a bit cleaner than :meth:`TwythonStreamer.statuses_filter()`, 29 | :meth:`statuses_sample()`, etc. all being single methods in 30 | :class:`TwythonStreamer`. 31 | 32 | """ 33 | def __init__(self, streamer): 34 | self.streamer = streamer 35 | self.params = None 36 | 37 | def filter(self, **params): 38 | r"""Stream statuses/filter 39 | 40 | :param \*\*params: Parameters to send with your stream request 41 | 42 | Accepted params found at: 43 | https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter 44 | """ 45 | url = 'https://stream.twitter.com/%s/statuses/filter.json' \ 46 | % self.streamer.api_version 47 | self.streamer._request(url, 'POST', params=params) 48 | 49 | def sample(self, **params): 50 | r"""Stream statuses/sample 51 | 52 | :param \*\*params: Parameters to send with your stream request 53 | 54 | Accepted params found at: 55 | https://developer.twitter.com/en/docs/tweets/sample-realtime/api-reference/get-statuses-sample 56 | """ 57 | url = 'https://stream.twitter.com/%s/statuses/sample.json' \ 58 | % self.streamer.api_version 59 | self.streamer._request(url, params=params) 60 | 61 | def firehose(self, **params): 62 | r"""Stream statuses/firehose 63 | 64 | :param \*\*params: Parameters to send with your stream request 65 | 66 | Accepted params found at: 67 | https://dev.twitter.com/docs/api/1.1/get/statuses/firehose 68 | """ 69 | url = 'https://stream.twitter.com/%s/statuses/firehose.json' \ 70 | % self.streamer.api_version 71 | self.streamer._request(url, params=params) 72 | 73 | def set_dynamic_filter(self, **params): 74 | r"""Set/update statuses/filter 75 | 76 | :param \*\*params: Parameters to send with your stream request 77 | 78 | Accepted params found at: 79 | https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter 80 | """ 81 | self.params = params 82 | 83 | def dynamic_filter(self): 84 | """Stream statuses/filter with dynamic parameters""" 85 | 86 | url = 'https://stream.twitter.com/%s/statuses/filter.json' \ 87 | % self.streamer.api_version 88 | self.streamer._request(url, 'POST', params=self.params) 89 | --------------------------------------------------------------------------------