Making twython work (again?) in Python 3

- Added a ``HISTORY.rst`` to start tracking history of changes
- Updated ``twitter_endpoints.py`` to ``endpoints.py`` for cleanliness
- Removed twython3k directory, no longer needed
- Added ``compat.py`` for compatability with Python 2.6 and greater
- Added some ascii art, moved description of Twython and ``__author__`` to ``__init__.py``
- 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)
- Removed ``find_packages()`` from ``setup.py``, only one package -- we can
just define it
- 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``
- Removed ``base_url`` from ``endpoints.py`` because we're just repeating it in
``Twython.__init__``
- ``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)
- Updated README to better reflect current Twython codebase
- Added ``warnings.simplefilter('default')`` line in ``twython.py`` for Python 2.7 and greater to display Deprecation Warnings in console
- Added Deprecation Warnings for usage of ``twitter_token``, ``twitter_secret`` and ``callback_url`` in ``Twython.__init__``
- Headers now always include the User-Agent as Twython vXX unless User-Agent is overwritten
- 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
- 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
This commit is contained in:
Mike Helmick 2013-04-17 18:59:11 -04:00
parent 8ecc55b5ad
commit bb019d3a57
16 changed files with 306 additions and 1000 deletions

39
.gitignore vendored
View file

@ -1,5 +1,36 @@
*.pyc *.py[cod]
build
# C extensions
*.so
# Packages
*.egg
*.egg-info
dist dist
twython.egg-info build
*.swp eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
__pycache__
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
nosetests.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject

View file

@ -13,7 +13,7 @@ Development Lead
Patches and Suggestions Patches and Suggestions
```````````````````````` ````````````````````````
- `Mike Helmick <https://github.com/michaelhelmick>`_, multiple fixes and proper ``requests`` integration. Too much to list here. - `Mike Helmick <https://github.com/michaelhelmick>`_, multiple fixes and proper ``requests`` integration, Python 3 compatibility, too much to list here.
- `kracekumar <https://github.com/kracekumar>`_, early ``requests`` work and various fixes. - `kracekumar <https://github.com/kracekumar>`_, early ``requests`` work and various fixes.
- `Erik Scheffers <https://github.com/eriks5>`_, various fixes regarding OAuth callback URLs. - `Erik Scheffers <https://github.com/eriks5>`_, various fixes regarding OAuth callback URLs.
- `Jordan Bouvier <https://github.com/jbouvier>`_, various fixes regarding OAuth callback URLs. - `Jordan Bouvier <https://github.com/jbouvier>`_, various fixes regarding OAuth callback URLs.

58
HISTORY.rst Normal file
View file

@ -0,0 +1,58 @@
History
-------
2.8.0 (2013-xx-xx)
++++++++++++++++++
- Added a ``HISTORY.rst`` to start tracking history of changes
- Updated ``twitter_endpoints.py`` to ``endpoints.py`` for cleanliness
- Removed twython3k directory, no longer needed
- Added ``compat.py`` for compatability with Python 2.6 and greater
- Added some ascii art, moved description of Twython and ``__author__`` to ``__init__.py``
- 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)
- Removed ``find_packages()`` from ``setup.py``, only one package -- we can
just define it
- 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``
- Removed ``base_url`` from ``endpoints.py`` because we're just repeating it in
``Twython.__init__``
- ``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)
- Updated README to better reflect current Twython codebase
- Added ``warnings.simplefilter('default')`` line in ``twython.py`` for Python 2.7 and greater to display Deprecation Warnings in console
- Added Deprecation Warnings for usage of ``twitter_token``, ``twitter_secret`` and ``callback_url`` in ``Twython.__init__``
- Headers now always include the User-Agent as Twython vXX unless User-Agent is overwritten
- 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
- 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
2.7.3 (2013-04-12)
++++++++++++++++++
- Fixed issue where Twython Exceptions were not being logged correctly
2.7.2 (2013-04-08)
++++++++++++++++++
- Fixed ``AttributeError`` when trying to decode the JSON response via ``Response.json()``
2.7.1 (2013-04-08)
++++++++++++++++++
- Removed ``simplejson`` dependency
- Fixed ``destroyDirectMessage``, ``createBlock``, ``destroyBlock`` endpoints in ``twitter_endpoints.py``
- Added ``getProfileBannerSizes`` method to ``twitter_endpoints.py``
- Made oauth_verifier argument required in ``get_authorized_tokens``
- Update ``updateProfileBannerImage`` to use v1.1 endpoint
2.7.0 (2013-04-04)
++++++++++++++++++
- New ``showOwnedLists`` method
2.7.0 (2013-03-31)
++++++++++++++++++
- Added missing slash to ``getMentionsTimeline`` in ``twitter_endpoints.py``
2.6.0 (2013-03-29)
++++++++++++++++++
- Updated ``twitter_endpoints.py`` to better reflect order of API endpoints on the Twitter API v1.1 docs site

View file

@ -1,3 +1,3 @@
include LICENSE README.md README.rst include LICENSE README.md README.rst HISTORY.rst
recursive-include examples * recursive-include examples *
recursive-exclude examples *.pyc recursive-exclude examples *.pyc

View file

@ -10,12 +10,13 @@ Features
- User information - User information
- Twitter lists - Twitter lists
- Timelines - Timelines
- User avatar URL - Direct Messages
- and anything found in [the docs](https://dev.twitter.com/docs/api/1.1) - and anything found in [the docs](https://dev.twitter.com/docs/api/1.1)
* Image Uploading! * Image Uploading!
- **Update user status with an image** - **Update user status with an image**
- Change user avatar - Change user avatar
- Change user background image - Change user background image
- Change user banner image
Installation Installation
------------ ------------
@ -36,11 +37,9 @@ Usage
```python ```python
from twython import Twython from twython import Twython
t = Twython(app_key=app_key, t = Twython(app_key, app_secret)
app_secret=app_secret,
callback_url='http://google.com/')
auth_props = t.get_authentication_tokens() auth_props = t.get_authentication_tokens(callback_url='http://google.com')
oauth_token = auth_props['oauth_token'] oauth_token = auth_props['oauth_token']
oauth_token_secret = auth_props['oauth_token_secret'] oauth_token_secret = auth_props['oauth_token_secret']
@ -55,41 +54,53 @@ Be sure you have a URL set up to handle the callback after the user has allowed
```python ```python
from twython import Twython from twython import Twython
''' # oauth_token_secret comes from the previous step
oauth_token and oauth_token_secret come from the previous step # if needed, store that in a session variable or something.
if needed, store those in a session variable or something. oauth_verifier from the previous call is now required to pass to get_authorized_tokens # oauth_verifier and oauth_token from the previous call is now REQUIRED # to pass to get_authorized_tokens
'''
t = Twython(app_key=app_key, # In Django, to get the oauth_verifier and oauth_token from the callback
app_secret=app_secret, # url querystring, you might do something like this:
oauth_token=oauth_token, # oauth_token = request.GET.get('oauth_token')
oauth_token_secret=oauth_token_secret) # oauth_verifier = request.GET.get('oauth_verifier')
t = Twython(app_key, app_secret,
oauth_token, oauth_token_secret)
auth_tokens = t.get_authorized_tokens(oauth_verifier) auth_tokens = t.get_authorized_tokens(oauth_verifier)
print auth_tokens print auth_tokens
``` ```
*Function definitions (i.e. getHomeTimeline()) can be found by reading over twython/twitter_endpoints.py* *Function definitions (i.e. getHomeTimeline()) can be found by reading over twython/endpoints.py*
###### Getting a user home timeline ###### Getting a user home timeline
```python ```python
from twython import Twython from twython import Twython
''' # oauth_token and oauth_token_secret are the final tokens produced
oauth_token and oauth_token_secret are the final tokens produced # from the 'Handling the callback' step
from the `Handling the callback` step
'''
t = Twython(app_key=app_key, t = Twython(app_key, app_secret,
app_secret=app_secret, oauth_token, oauth_token_secret)
oauth_token=oauth_token,
oauth_token_secret=oauth_token_secret)
# Returns an dict of the user home timeline # Returns an dict of the user home timeline
print t.getHomeTimeline() print t.getHomeTimeline()
``` ```
###### Catching exceptions
> Twython offers three Exceptions currently: TwythonError, TwythonAuthError and TwythonRateLimitError
```python
from twython import Twython, TwythonAuthError
t = Twython(MY_WRONG_APP_KEY, MY_WRONG_APP_SECRET,
BAD_OAUTH_TOKEN, BAD_OAUTH_TOKEN_SECRET)
try:
t.verifyCredentials()
except TwythonAuthError as e:
print e
```
###### Streaming API ###### Streaming API
*Usage is as follows; it's designed to be open-ended enough that you can adapt it to higher-level (read: Twitter must give you access) *Usage is as follows; it's designed to be open-ended enough that you can adapt it to higher-level (read: Twitter must give you access)
streams.* streams.*
@ -136,12 +147,7 @@ from you using them by this library.
Twython 3k Twython 3k
---------- ----------
There's an experimental version of Twython that's made for Python 3k. This is currently not guaranteed to Full compatiabilty with Python 3 is now available seamlessly in the main Twython package. The Twython 3k package has been removed as of Twython 2.8.0
work in all situations, but it's provided so that others can grab it and hack on it.
If you choose to try it out, be aware of this.
**OAuth is now working thanks to updates from [Hades](https://github.com/hades). You'll need to grab
his [Python 3 branch for python-oauth2](https://github.com/hades/python-oauth2/tree/python3) to have it work, though.**
Questions, Comments, etc? Questions, Comments, etc?
------------------------- -------------------------
@ -150,8 +156,6 @@ at ryan@venodesigns.net.
You can also follow me on Twitter - **[@ryanmcgrath](http://twitter.com/ryanmcgrath)**. You can also follow me on Twitter - **[@ryanmcgrath](http://twitter.com/ryanmcgrath)**.
Twython is released under an MIT License - see the LICENSE file for more information.
Want to help? Want to help?
------------- -------------
Twython is useful, but ultimately only as useful as the people using it (say that ten times fast!). If you'd like to help, write example code, contribute patches, document things on the wiki, tweet about it. Your help is always appreciated! Twython is useful, but ultimately only as useful as the people using it (say that ten times fast!). If you'd like to help, write example code, contribute patches, document things on the wiki, tweet about it. Your help is always appreciated!

View file

@ -9,12 +9,13 @@ Features
- User information - User information
- Twitter lists - Twitter lists
- Timelines - Timelines
- User avatar URL - Direct Messages
- and anything found in `the docs <https://dev.twitter.com/docs/api/1.1>`_ - and anything found in `the docs <https://dev.twitter.com/docs/api/1.1>`_
* Image Uploading! * Image Uploading!
- **Update user status with an image** - **Update user status with an image**
- Change user avatar - Change user avatar
- Change user background image - Change user background image
- Change user banner image
Installation Installation
------------ ------------
@ -37,13 +38,12 @@ Usage
Authorization URL Authorization URL
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
:: ::
from twython import Twython from twython import Twython
t = Twython(app_key=app_key, t = Twython(app_key, app_secret)
app_secret=app_secret,
callback_url='http://google.com/')
auth_props = t.get_authentication_tokens() auth_props = t.get_authentication_tokens(callback_url='http://google.com')
oauth_token = auth_props['oauth_token'] oauth_token = auth_props['oauth_token']
oauth_token_secret = auth_props['oauth_token_secret'] oauth_token_secret = auth_props['oauth_token_secret']
@ -56,41 +56,59 @@ Handling the callback
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
:: ::
'''
oauth_token and oauth_token_secret come from the previous step
if needed, store those in a session variable or something. oauth_verifier from the previous call is now required to pass to get_authorized_tokens
'''
from twython import Twython from twython import Twython
t = Twython(app_key=app_key, # oauth_token_secret comes from the previous step
app_secret=app_secret, # if needed, store that in a session variable or something.
oauth_token=oauth_token, # oauth_verifier and oauth_token from the previous call is now REQUIRED # to pass to get_authorized_tokens
oauth_token_secret=oauth_token_secret)
# In Django, to get the oauth_verifier and oauth_token from the callback
# url querystring, you might do something like this:
# oauth_token = request.GET.get('oauth_token')
# oauth_verifier = request.GET.get('oauth_verifier')
t = Twython(app_key, app_secret,
oauth_token, oauth_token_secret)
auth_tokens = t.get_authorized_tokens(oauth_verifier) auth_tokens = t.get_authorized_tokens(oauth_verifier)
print auth_tokens print auth_tokens
*Function definitions (i.e. getHomeTimeline()) can be found by reading over twython/twitter_endpoints.py* *Function definitions (i.e. getHomeTimeline()) can be found by reading over twython/endpoints.py*
Getting a user home timeline Getting a user home timeline
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:: ::
'''
oauth_token and oauth_token_secret are the final tokens produced
from the `Handling the callback` step
'''
from twython import Twython from twython import Twython
t = Twython(app_key=app_key, # oauth_token and oauth_token_secret are the final tokens produced
app_secret=app_secret, # from the 'Handling the callback' step
oauth_token=oauth_token,
oauth_token_secret=oauth_token_secret) t = Twython(app_key, app_secret,
oauth_token, oauth_token_secret)
# Returns an dict of the user home timeline # Returns an dict of the user home timeline
print t.getHomeTimeline() print t.getHomeTimeline()
Catching exceptions
~~~~~~~~~~~~~~~~~~~
Twython offers three Exceptions currently: ``TwythonError``, ``TwythonAuthError`` and ``TwythonRateLimitError``
::
from twython import Twython, TwythonAuthError
t = Twython(MY_WRONG_APP_KEY, MY_WRONG_APP_SECRET,
BAD_OAUTH_TOKEN, BAD_OAUTH_TOKEN_SECRET)
try:
t.verifyCredentials()
except TwythonAuthError as e:
print e
Streaming API Streaming API
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
*Usage is as follows; it's designed to be open-ended enough that you can adapt it to higher-level (read: Twitter must give you access) *Usage is as follows; it's designed to be open-ended enough that you can adapt it to higher-level (read: Twitter must give you access)
@ -143,12 +161,7 @@ from you using them by this library.
Twython 3k Twython 3k
---------- ----------
There's an experimental version of Twython that's made for Python 3k. This is currently not guaranteed to Full compatiabilty with Python 3 is now available seamlessly in the main Twython package. The Twython 3k package has been removed as of Twython 2.8.0
work in all situations, but it's provided so that others can grab it and hack on it.
If you choose to try it out, be aware of this.
**OAuth is now working thanks to updates from [Hades](https://github.com/hades). You'll need to grab
his [Python 3 branch for python-oauth2](https://github.com/hades/python-oauth2/tree/python3) to have it work, though.**
Questions, Comments, etc? Questions, Comments, etc?
------------------------- -------------------------
@ -156,8 +169,6 @@ My hope is that Twython is so simple that you'd never *have* to ask any question
You can also follow me on Twitter - `@ryanmcgrath <https://twitter.com/ryanmcgrath>`_ You can also follow me on Twitter - `@ryanmcgrath <https://twitter.com/ryanmcgrath>`_
*Twython is released under an MIT License - see the LICENSE file for more information.*
Want to help? Want to help?
------------- -------------
Twython is useful, but ultimately only as useful as the people using it (say that ten times fast!). If you'd like to help, write example code, contribute patches, document things on the wiki, tweet about it. Your help is always appreciated! Twython is useful, but ultimately only as useful as the people using it (say that ten times fast!). If you'd like to help, write example code, contribute patches, document things on the wiki, tweet about it. Your help is always appreciated!

View file

@ -1,14 +1,25 @@
import os
import sys
from twython.version import __version__
from setuptools import setup from setuptools import setup
from setuptools import find_packages
__author__ = 'Ryan McGrath <ryan@venodesigns.net>' __author__ = 'Ryan McGrath <ryan@venodesigns.net>'
__version__ = '2.7.3'
packages = [
'twython'
]
if sys.argv[-1] == 'publish':
os.system('python setup.py sdist upload')
sys.exit()
setup( setup(
# Basic package information. # Basic package information.
name='twython', name='twython',
version=__version__, version=__version__,
packages=find_packages(), packages=packages,
# Packaging options. # Packaging options.
include_package_data=True, include_package_data=True,

View file

@ -1,2 +1,23 @@
from twython import Twython # ______ __ __
# /_ __/_ __ __ __ / /_ / /_ ____ ____
# / / | | /| / // / / // __// __ \ / __ \ / __ \
# / / | |/ |/ // /_/ // /_ / / / // /_/ // / / /
# /_/ |__/|__/ \__, / \__//_/ /_/ \____//_/ /_/
# /____/
"""
Twython
-------
Twython is a library for Python that wraps the Twitter API.
It aims to abstract away all the API endpoints, so that additions to the library
and/or the Twitter API won't cause any overall problems.
Questions, comments? ryan@venodesigns.net
"""
__author__ = 'Ryan McGrath <ryan@venodesigns.net>'
from .twython import Twython
from .exceptions import TwythonError, TwythonRateLimitError, TwythonAuthError from .exceptions import TwythonError, TwythonRateLimitError, TwythonAuthError

38
twython/compat.py Normal file
View file

@ -0,0 +1,38 @@
import sys
_ver = sys.version_info
#: Python 2.x?
is_py2 = (_ver[0] == 2)
#: Python 3.x?
is_py3 = (_ver[0] == 3)
try:
import simplejson as json
except ImportError:
import json
try:
from urlparse import parse_qsl
except ImportError:
from cgi import parse_qsl
if is_py2:
from urllib import urlencode, quote_plus
builtin_str = str
bytes = str
str = unicode
basestring = basestring
numeric_types = (int, long, float)
elif is_py3:
from urllib.parse import urlencode, quote_plus
builtin_str = str
str = str
bytes = bytes
basestring = (str, bytes)
numeric_types = (int, float)

View file

@ -15,9 +15,6 @@
https://dev.twitter.com/docs/api/1.1 https://dev.twitter.com/docs/api/1.1
""" """
# Base Twitter API url, no need to repeat this junk...
base_url = 'http://api.twitter.com/{{version}}'
api_table = { api_table = {
# Timelines # Timelines
'getMentionsTimeline': { 'getMentionsTimeline': {

View file

@ -1,4 +1,4 @@
from twitter_endpoints import twitter_http_status_codes from .endpoints import twitter_http_status_codes
class TwythonError(Exception): class TwythonError(Exception):

View file

@ -1,40 +1,19 @@
"""
Twython is a library for Python that wraps the Twitter API.
It aims to abstract away all the API endpoints, so that additions to the library
and/or the Twitter API won't cause any overall problems.
Questions, comments? ryan@venodesigns.net
"""
__author__ = "Ryan McGrath <ryan@venodesigns.net>"
__version__ = "2.7.3"
import urllib
import re import re
import warnings import warnings
warnings.simplefilter('default') # For Python 2.7 >
import requests import requests
from requests_oauthlib import OAuth1 from requests_oauthlib import OAuth1
try: from .compat import json, urlencode, parse_qsl, quote_plus
from urlparse import parse_qsl from .endpoints import api_table
except ImportError:
from cgi import parse_qsl
# Twython maps keyword based arguments to Twitter API endpoints. The endpoints
# table is a file with a dictionary of every API endpoint that Twython supports.
from twitter_endpoints import base_url, api_table
from .exceptions import TwythonError, TwythonAuthError, TwythonRateLimitError from .exceptions import TwythonError, TwythonAuthError, TwythonRateLimitError
from .version import __version__
try:
import simplejson as json
except ImportError:
import json
class Twython(object): class Twython(object):
def __init__(self, app_key=None, app_secret=None, oauth_token=None, oauth_token_secret=None, def __init__(self, app_key=None, app_secret=None, oauth_token=None, oauth_token_secret=None,
headers=None, callback_url=None, twitter_token=None, twitter_secret=None, proxies=None, version='1.1'): headers=None, proxies=None, version='1.1', callback_url=None, twitter_token=None, twitter_secret=None):
"""Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). """Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below).
:param app_key: (optional) Your applications key :param app_key: (optional) Your applications key
@ -46,46 +25,55 @@ class Twython(object):
:param proxies: (optional) A dictionary of proxies, for example {"http":"proxy.example.org:8080", "https":"proxy.example.org:8081"}. :param proxies: (optional) A dictionary of proxies, for example {"http":"proxy.example.org:8080", "https":"proxy.example.org:8081"}.
""" """
# Needed for hitting that there API. # API urls, OAuth urls and API version; needed for hitting that there API.
self.api_version = version self.api_version = version
self.api_url = 'https://api.twitter.com/%s' self.api_url = 'https://api.twitter.com/%s'
self.request_token_url = self.api_url % 'oauth/request_token' self.request_token_url = self.api_url % 'oauth/request_token'
self.access_token_url = self.api_url % 'oauth/access_token' self.access_token_url = self.api_url % 'oauth/access_token'
self.authenticate_url = self.api_url % 'oauth/authenticate' self.authenticate_url = self.api_url % 'oauth/authenticate'
# Enforce unicode on keys and secrets self.app_key = app_key or twitter_token
self.app_key = app_key and unicode(app_key) or twitter_token and unicode(twitter_token) self.app_secret = app_secret or twitter_secret
self.app_secret = app_key and unicode(app_secret) or twitter_secret and unicode(twitter_secret) self.oauth_token = oauth_token
self.oauth_token_secret = oauth_token_secret
self.oauth_token = oauth_token and u'%s' % oauth_token
self.oauth_token_secret = oauth_token_secret and u'%s' % oauth_token_secret
self.callback_url = callback_url self.callback_url = callback_url
# If there's headers, set them, otherwise be an embarassing parent for their own good. if twitter_token or twitter_secret:
self.headers = headers or {'User-Agent': 'Twython v' + __version__} warnings.warn(
'Instead of twitter_token or twitter_secret, please use app_key or app_secret (respectively).',
DeprecationWarning,
stacklevel=2
)
# Allow for unauthenticated requests if callback_url:
self.client = requests.Session() warnings.warn(
self.client.proxies = proxies 'Please pass callback_url to the get_authentication_tokens method rather than Twython.__init__',
DeprecationWarning,
stacklevel=2
)
self.headers = {'User-Agent': 'Twython v' + __version__}
if headers:
self.headers.update(headers)
# Generate OAuth authentication object for the request
# If no keys/tokens are passed to __init__, self.auth=None allows for
# unauthenticated requests, although I think all v1.1 requests need auth
self.auth = None self.auth = None
if self.app_key is not None and self.app_secret is not None and \
self.oauth_token is None and self.oauth_token_secret is None:
self.auth = OAuth1(self.app_key, self.app_secret)
if self.app_key is not None and self.app_secret is not None and \ if self.app_key is not None and self.app_secret is not None and \
self.oauth_token is None and self.oauth_token_secret is None: self.oauth_token is not None and self.oauth_token_secret is not None:
self.auth = OAuth1(self.app_key, self.app_secret, self.auth = OAuth1(self.app_key, self.app_secret,
signature_type='auth_header') self.oauth_token, self.oauth_token_secret)
if self.app_key is not None and self.app_secret is not None and \ self.client = requests.Session()
self.oauth_token is not None and self.oauth_token_secret is not None: self.client.headers = self.headers
self.auth = OAuth1(self.app_key, self.app_secret, self.client.proxies = proxies
self.oauth_token, self.oauth_token_secret, self.client.auth = self.auth
signature_type='auth_header')
if self.auth is not None:
self.client = requests.Session()
self.client.headers = self.headers
self.client.auth = self.auth
self.client.proxies = proxies
# register available funcs to allow listing name when debugging. # register available funcs to allow listing name when debugging.
def setFunc(key): def setFunc(key):
@ -101,8 +89,8 @@ class Twython(object):
fn = api_table[api_call] fn = api_table[api_call]
url = re.sub( url = re.sub(
'\{\{(?P<m>[a-zA-Z_]+)\}\}', '\{\{(?P<m>[a-zA-Z_]+)\}\}',
lambda m: "%s" % kwargs.get(m.group(1), self.api_version), lambda m: "%s" % kwargs.get(m.group(1)),
base_url + fn['url'] self.api_url % self.api_version + fn['url']
) )
content = self._request(url, method=fn['method'], params=kwargs) content = self._request(url, method=fn['method'], params=kwargs)
@ -114,15 +102,7 @@ class Twython(object):
code twice, right? ;) code twice, right? ;)
''' '''
method = method.lower() method = method.lower()
if not method in ('get', 'post'):
raise TwythonError('Method must be of GET or POST')
params = params or {} params = params or {}
# requests doesn't like items that can't be converted to unicode,
# so let's be nice and do that for the user
for k, v in params.items():
if isinstance(v, (int, bool)):
params[k] = u'%s' % v
func = getattr(self.client, method) func = getattr(self.client, method)
if method == 'get': if method == 'get':
@ -176,7 +156,7 @@ class Twython(object):
error_code=response.status_code, error_code=response.status_code,
retry_after=response.headers.get('retry-after')) retry_after=response.headers.get('retry-after'))
# if we have a json error here, then it's not an official TwitterAPI error # if we have a json error here, then it's not an official Twitter API error
if json_error and not response.status_code in (200, 201, 202): if json_error and not response.status_code in (200, 201, 202):
raise TwythonError('Response was not valid JSON, unable to decode.') raise TwythonError('Response was not valid JSON, unable to decode.')
@ -228,23 +208,23 @@ class Twython(object):
return self._last_call['headers'][header] return self._last_call['headers'][header]
return self._last_call return self._last_call
def get_authentication_tokens(self, force_login=False, screen_name=''): def get_authentication_tokens(self, callback_url=None, force_login=False, screen_name=''):
"""Returns an authorization URL for a user to hit. """Returns a dict including an authorization URL (auth_url) to direct a user to
:param callback_url: (optional.. for now) Url the user is returned to after they authorize your app
:param force_login: (optional) Forces the user to enter their credentials to ensure the correct users account is authorized. :param force_login: (optional) Forces the user to enter their credentials to ensure the correct users account is authorized.
:param app_secret: (optional) If forced_login is set OR user is not currently logged in, Prefills the username input box of the OAuth login screen with the given value :param app_secret: (optional) If forced_login is set OR user is not currently logged in, Prefills the username input box of the OAuth login screen with the given value
""" """
request_args = {} callback_url = callback_url or self.callback_url
if self.callback_url: request_args = {'oauth_callback': callback_url}
request_args['oauth_callback'] = self.callback_url
response = self.client.get(self.request_token_url, params=request_args) response = self.client.get(self.request_token_url, params=request_args)
if response.status_code == 401: if response.status_code == 401:
raise TwythonAuthError(response.content, error_code=response.status_code) raise TwythonAuthError(response.content, error_code=response.status_code)
elif response.status_code != 200: elif response.status_code != 200:
raise TwythonError(response.content, error_code=response.status_code) raise TwythonError(response.content, error_code=response.status_code)
request_tokens = dict(parse_qsl(response.content)) request_tokens = dict(parse_qsl(response.content.decode('utf-8')))
if not request_tokens: if not request_tokens:
raise TwythonError('Unable to decode request tokens.') raise TwythonError('Unable to decode request tokens.')
@ -261,18 +241,20 @@ class Twython(object):
}) })
# Use old-style callback argument if server didn't accept new-style # Use old-style callback argument if server didn't accept new-style
if self.callback_url and not oauth_callback_confirmed: if callback_url and not oauth_callback_confirmed:
auth_url_params['oauth_callback'] = self.callback_url auth_url_params['oauth_callback'] = self.callback_url
request_tokens['auth_url'] = self.authenticate_url + '?' + urllib.urlencode(auth_url_params) request_tokens['auth_url'] = self.authenticate_url + '?' + urlencode(auth_url_params)
return request_tokens return request_tokens
def get_authorized_tokens(self, oauth_verifier): def get_authorized_tokens(self, oauth_verifier):
"""Returns authorized tokens after they go through the auth_url phase. """Returns authorized tokens after they go through the auth_url phase.
:param oauth_verifier: (required) The oauth_verifier retrieved from the callback url querystring
""" """
response = self.client.get(self.access_token_url, params={'oauth_verifier': oauth_verifier}) response = self.client.get(self.access_token_url, params={'oauth_verifier': oauth_verifier})
authorized_tokens = dict(parse_qsl(response.content)) authorized_tokens = dict(parse_qsl(response.content.decode('utf-8')))
if not authorized_tokens: if not authorized_tokens:
raise TwythonError('Unable to decode authorized tokens.') raise TwythonError('Unable to decode authorized tokens.')
@ -308,7 +290,7 @@ class Twython(object):
@staticmethod @staticmethod
def constructApiURL(base_url, params): def constructApiURL(base_url, params):
return base_url + "?" + "&".join(["%s=%s" % (Twython.unicode2utf8(key), urllib.quote_plus(Twython.unicode2utf8(value))) for (key, value) in params.iteritems()]) return base_url + "?" + "&".join(["%s=%s" % (Twython.unicode2utf8(key), quote_plus(Twython.unicode2utf8(value))) for (key, value) in params.iteritems()])
def searchGen(self, search_query, **kwargs): def searchGen(self, search_query, **kwargs):
""" Returns a generator of tweets that match a specified query. """ Returns a generator of tweets that match a specified query.
@ -331,7 +313,7 @@ class Twython(object):
yield tweet yield tweet
if 'page' not in kwargs: if 'page' not in kwargs:
kwargs['page'] = '2' kwargs['page'] = 2
else: else:
try: try:
kwargs['page'] = int(kwargs['page']) kwargs['page'] = int(kwargs['page'])
@ -416,7 +398,7 @@ class Twython(object):
only API version for Twitter that supports this call only API version for Twitter that supports this call
**params - You may pass items that are taken in this doc **params - You may pass items that are taken in this doc
(https://dev.twitter.com/docs/api/1/post/account/update_profile_banner) (https://dev.twitter.com/docs/api/1.1/post/account/update_profile_banner)
""" """
url = 'https://api.twitter.com/%s/account/update_profile_banner.json' % version url = 'https://api.twitter.com/%s/account/update_profile_banner.json' % version
return self._media_update(url, return self._media_update(url,

1
twython/version.py Normal file
View file

@ -0,0 +1 @@
__version__ = '2.7.3'

View file

@ -1 +0,0 @@
from .twython import Twython

View file

@ -1,334 +0,0 @@
"""
A huge map of every Twitter API endpoint to a function definition in Twython.
Parameters that need to be embedded in the URL are treated with mustaches, e.g:
{{version}}, etc
When creating new endpoint definitions, keep in mind that the name of the mustache
will be replaced with the keyword that gets passed in to the function at call time.
i.e, in this case, if I pass version = 47 to any function, {{version}} will be replaced
with 47, instead of defaulting to 1 (said defaulting takes place at conversion time).
"""
# Base Twitter API url, no need to repeat this junk...
base_url = 'http://api.twitter.com/{{version}}'
api_table = {
'getRateLimitStatus': {
'url': '/account/rate_limit_status.json',
'method': 'GET',
},
'verifyCredentials': {
'url': '/account/verify_credentials.json',
'method': 'GET',
},
'endSession' : {
'url': '/account/end_session.json',
'method': 'POST',
},
# Timeline methods
'getPublicTimeline': {
'url': '/statuses/public_timeline.json',
'method': 'GET',
},
'getHomeTimeline': {
'url': '/statuses/home_timeline.json',
'method': 'GET',
},
'getUserTimeline': {
'url': '/statuses/user_timeline.json',
'method': 'GET',
},
'getFriendsTimeline': {
'url': '/statuses/friends_timeline.json',
'method': 'GET',
},
# Interfacing with friends/followers
'getUserMentions': {
'url': '/statuses/mentions.json',
'method': 'GET',
},
'getFriendsStatus': {
'url': '/statuses/friends.json',
'method': 'GET',
},
'getFollowersStatus': {
'url': '/statuses/followers.json',
'method': 'GET',
},
'createFriendship': {
'url': '/friendships/create.json',
'method': 'POST',
},
'destroyFriendship': {
'url': '/friendships/destroy.json',
'method': 'POST',
},
'getFriendsIDs': {
'url': '/friends/ids.json',
'method': 'GET',
},
'getFollowersIDs': {
'url': '/followers/ids.json',
'method': 'GET',
},
'getIncomingFriendshipIDs': {
'url': '/friendships/incoming.json',
'method': 'GET',
},
'getOutgoingFriendshipIDs': {
'url': '/friendships/outgoing.json',
'method': 'GET',
},
# Retweets
'reTweet': {
'url': '/statuses/retweet/{{id}}.json',
'method': 'POST',
},
'getRetweets': {
'url': '/statuses/retweets/{{id}}.json',
'method': 'GET',
},
'retweetedOfMe': {
'url': '/statuses/retweets_of_me.json',
'method': 'GET',
},
'retweetedByMe': {
'url': '/statuses/retweeted_by_me.json',
'method': 'GET',
},
'retweetedToMe': {
'url': '/statuses/retweeted_to_me.json',
'method': 'GET',
},
# User methods
'showUser': {
'url': '/users/show.json',
'method': 'GET',
},
'searchUsers': {
'url': '/users/search.json',
'method': 'GET',
},
'lookupUser': {
'url': '/users/lookup.json',
'method': 'GET',
},
# Status methods - showing, updating, destroying, etc.
'showStatus': {
'url': '/statuses/show/{{id}}.json',
'method': 'GET',
},
'updateStatus': {
'url': '/statuses/update.json',
'method': 'POST',
},
'destroyStatus': {
'url': '/statuses/destroy/{{id}}.json',
'method': 'POST',
},
# Direct Messages - getting, sending, effing, etc.
'getDirectMessages': {
'url': '/direct_messages.json',
'method': 'GET',
},
'getSentMessages': {
'url': '/direct_messages/sent.json',
'method': 'GET',
},
'sendDirectMessage': {
'url': '/direct_messages/new.json',
'method': 'POST',
},
'destroyDirectMessage': {
'url': '/direct_messages/destroy/{{id}}.json',
'method': 'POST',
},
# Friendship methods
'checkIfFriendshipExists': {
'url': '/friendships/exists.json',
'method': 'GET',
},
'showFriendship': {
'url': '/friendships/show.json',
'method': 'GET',
},
# Profile methods
'updateProfile': {
'url': '/account/update_profile.json',
'method': 'POST',
},
'updateProfileColors': {
'url': '/account/update_profile_colors.json',
'method': 'POST',
},
'myTotals': {
'url' : '/account/totals.json',
'method': 'GET',
},
# Favorites methods
'getFavorites': {
'url': '/favorites.json',
'method': 'GET',
},
'createFavorite': {
'url': '/favorites/create/{{id}}.json',
'method': 'POST',
},
'destroyFavorite': {
'url': '/favorites/destroy/{{id}}.json',
'method': 'POST',
},
# Blocking methods
'createBlock': {
'url': '/blocks/create/{{id}}.json',
'method': 'POST',
},
'destroyBlock': {
'url': '/blocks/destroy/{{id}}.json',
'method': 'POST',
},
'getBlocking': {
'url': '/blocks/blocking.json',
'method': 'GET',
},
'getBlockedIDs': {
'url': '/blocks/blocking/ids.json',
'method': 'GET',
},
'checkIfBlockExists': {
'url': '/blocks/exists.json',
'method': 'GET',
},
# Trending methods
'getCurrentTrends': {
'url': '/trends/current.json',
'method': 'GET',
},
'getDailyTrends': {
'url': '/trends/daily.json',
'method': 'GET',
},
'getWeeklyTrends': {
'url': '/trends/weekly.json',
'method': 'GET',
},
'availableTrends': {
'url': '/trends/available.json',
'method': 'GET',
},
'trendsByLocation': {
'url': '/trends/{{woeid}}.json',
'method': 'GET',
},
# Saved Searches
'getSavedSearches': {
'url': '/saved_searches.json',
'method': 'GET',
},
'showSavedSearch': {
'url': '/saved_searches/show/{{id}}.json',
'method': 'GET',
},
'createSavedSearch': {
'url': '/saved_searches/create.json',
'method': 'GET',
},
'destroySavedSearch': {
'url': '/saved_searches/destroy/{{id}}.json',
'method': 'GET',
},
# List API methods/endpoints. Fairly exhaustive and annoying in general. ;P
'createList': {
'url': '/{{username}}/lists.json',
'method': 'POST',
},
'updateList': {
'url': '/{{username}}/lists/{{list_id}}.json',
'method': 'POST',
},
'showLists': {
'url': '/{{username}}/lists.json',
'method': 'GET',
},
'getListMemberships': {
'url': '/{{username}}/lists/memberships.json',
'method': 'GET',
},
'getListSubscriptions': {
'url': '/{{username}}/lists/subscriptions.json',
'method': 'GET',
},
'deleteList': {
'url': '/{{username}}/lists/{{list_id}}.json',
'method': 'DELETE',
},
'getListTimeline': {
'url': '/{{username}}/lists/{{list_id}}/statuses.json',
'method': 'GET',
},
'getSpecificList': {
'url': '/{{username}}/lists/{{list_id}}/statuses.json',
'method': 'GET',
},
'addListMember': {
'url': '/{{username}}/{{list_id}}/members.json',
'method': 'POST',
},
'getListMembers': {
'url': '/{{username}}/{{list_id}}/members.json',
'method': 'GET',
},
'deleteListMember': {
'url': '/{{username}}/{{list_id}}/members.json',
'method': 'DELETE',
},
'getListSubscribers': {
'url': '/{{username}}/{{list_id}}/subscribers.json',
'method': 'GET',
},
'subscribeToList': {
'url': '/{{username}}/{{list_id}}/subscribers.json',
'method': 'POST',
},
'unsubscribeFromList': {
'url': '/{{username}}/{{list_id}}/subscribers.json',
'method': 'DELETE',
},
# The one-offs
'notificationFollow': {
'url': '/notifications/follow/follow.json',
'method': 'POST',
},
'notificationLeave': {
'url': '/notifications/leave/leave.json',
'method': 'POST',
},
'updateDeliveryService': {
'url': '/account/update_delivery_device.json',
'method': 'POST',
},
'reportSpam': {
'url': '/report_spam.json',
'method': 'POST',
},
}

View file

@ -1,513 +0,0 @@
#!/usr/bin/python
"""
Twython is a library for Python that wraps the Twitter API.
It aims to abstract away all the API endpoints, so that additions to the library
and/or the Twitter API won't cause any overall problems.
Questions, comments? ryan@venodesigns.net
"""
__author__ = "Ryan McGrath <ryan@venodesigns.net>"
__version__ = "1.4.7"
import cgi
import urllib.request, urllib.parse, urllib.error
import urllib.request, urllib.error, urllib.parse
import urllib.parse
import http.client
import httplib2
import mimetypes
import re
import inspect
import email.generator
import oauth2 as oauth
# Twython maps keyword based arguments to Twitter API endpoints. The endpoints
# table is a file with a dictionary of every API endpoint that Twython supports.
from twitter_endpoints import base_url, api_table
from urllib.error import HTTPError
# There are some special setups (like, oh, a Django application) where
# simplejson exists behind the scenes anyway. Past Python 2.6, this should
# never really cause any problems to begin with.
try:
# Python 2.6 and up
import json as simplejson
except ImportError:
# Seriously wtf is wrong with you if you get this Exception.
raise Exception("Twython3k requires a json library to work. http://www.undefined.org/python/")
# Try and gauge the old OAuth2 library spec. Versions 1.5 and greater no longer have the callback
# url as part of the request object; older versions we need to patch for Python 2.5... ugh. ;P
OAUTH_CALLBACK_IN_URL = False
OAUTH_LIB_SUPPORTS_CALLBACK = False
if not hasattr(oauth, '_version') or float(oauth._version.manual_verstr) <= 1.4:
OAUTH_CLIENT_INSPECTION = inspect.getargspec(oauth.Client.request)
try:
OAUTH_LIB_SUPPORTS_CALLBACK = 'callback_url' in OAUTH_CLIENT_INSPECTION.args
except AttributeError:
# Python 2.5 doesn't return named tuples, so don't look for an args section specifically.
OAUTH_LIB_SUPPORTS_CALLBACK = 'callback_url' in OAUTH_CLIENT_INSPECTION
else:
OAUTH_CALLBACK_IN_URL = True
class TwythonError(AttributeError):
"""
Generic error class, catch-all for most Twython issues.
Special cases are handled by APILimit and AuthError.
Note: To use these, the syntax has changed as of Twython 1.3. To catch these,
you need to explicitly import them into your code, e.g:
from twython import TwythonError, APILimit, AuthError
"""
def __init__(self, msg, error_code=None):
self.msg = msg
if error_code == 400:
raise APILimit(msg)
def __str__(self):
return repr(self.msg)
class TwythonAPILimit(TwythonError):
"""
Raised when you've hit an API limit. Try to avoid these, read the API
docs if you're running into issues here, Twython does not concern itself with
this matter beyond telling you that you've done goofed.
"""
def __init__(self, msg):
self.msg = msg
def __str__(self):
return repr(self.msg)
class APILimit(TwythonError):
"""
Raised when you've hit an API limit. Try to avoid these, read the API
docs if you're running into issues here, Twython does not concern itself with
this matter beyond telling you that you've done goofed.
DEPRECATED, you should be importing TwythonAPILimit instead. :)
"""
def __init__(self, msg):
self.msg = '%s\n Notice: APILimit is deprecated and soon to be removed, catch TwythonAPILimit instead!' % msg
def __str__(self):
return repr(self.msg)
class TwythonAuthError(TwythonError):
"""
Raised when you try to access a protected resource and it fails due to some issue with
your authentication.
"""
def __init__(self, msg):
self.msg = msg
def __str__(self):
return repr(self.msg)
class AuthError(TwythonError):
"""
Raised when you try to access a protected resource and it fails due to some issue with
your authentication.
DEPRECATED, you should be importing TwythonAuthError instead.
"""
def __init__(self, msg):
self.msg = '%s\n Notice: AuthLimit is deprecated and soon to be removed, catch TwythonAPILimit instead!' % msg
def __str__(self):
return repr(self.msg)
class Twython(object):
def __init__(self, twitter_token = None, twitter_secret = None, oauth_token = None, oauth_token_secret = None, headers=None, callback_url=None, client_args={}):
"""setup(self, oauth_token = None, headers = None)
Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below).
Parameters:
twitter_token - Given to you when you register your application with Twitter.
twitter_secret - Given to you when you register your application with Twitter.
oauth_token - If you've gone through the authentication process and have a token for this user,
pass it in and it'll be used for all requests going forward.
oauth_token_secret - see oauth_token; it's the other half.
headers - User agent header, dictionary style ala {'User-Agent': 'Bert'}
client_args - additional arguments for HTTP client (see httplib2.Http.__init__), e.g. {'timeout': 10.0}
** Note: versioning is not currently used by search.twitter functions; when Twitter moves their junk, it'll be supported.
"""
# Needed for hitting that there API.
self.request_token_url = 'https://twitter.com/oauth/request_token'
self.access_token_url = 'https://twitter.com/oauth/access_token'
self.authorize_url = 'https://twitter.com/oauth/authorize'
self.authenticate_url = 'https://twitter.com/oauth/authenticate'
self.twitter_token = twitter_token
self.twitter_secret = twitter_secret
self.oauth_token = oauth_token
self.oauth_secret = oauth_token_secret
self.callback_url = callback_url
# If there's headers, set them, otherwise be an embarassing parent for their own good.
self.headers = headers
if self.headers is None:
self.headers = {'User-agent': 'Twython Python Twitter Library v1.3'}
consumer = None
token = None
if self.twitter_token is not None and self.twitter_secret is not None:
consumer = oauth.Consumer(self.twitter_token, self.twitter_secret)
if self.oauth_token is not None and self.oauth_secret is not None:
token = oauth.Token(oauth_token, oauth_token_secret)
# Filter down through the possibilities here - if they have a token, if they're first stage, etc.
if consumer is not None and token is not None:
self.client = oauth.Client(consumer, token, **client_args)
elif consumer is not None:
self.client = oauth.Client(consumer, **client_args)
else:
# If they don't do authentication, but still want to request unprotected resources, we need an opener.
self.client = httplib2.Http(**client_args)
def setFunc(key):
return lambda **kwargs: self._constructFunc(key, **kwargs)
# register available funcs to allow listing name when debugging.
for key in api_table.keys():
self.__dict__[key] = setFunc(key)
def _constructFunc(self, api_call, **kwargs):
# Go through and replace any mustaches that are in our API url.
fn = api_table[api_call]
base = re.sub(
'\{\{(?P<m>[a-zA-Z_]+)\}\}',
lambda m: "%s" % kwargs.get(m.group(1), '1'), # The '1' here catches the API version. Slightly hilarious.
base_url + fn['url']
)
# Then open and load that shiiit, yo. TODO: check HTTP method and junk, handle errors/authentication
if fn['method'] == 'POST':
resp, content = self.client.request(base, fn['method'], urllib.parse.urlencode(dict([k, Twython.encode(v)] for k, v in list(kwargs.items()))), headers = self.headers)
else:
url = base + "?" + "&".join(["%s=%s" %(key, value) for (key, value) in list(kwargs.items())])
resp, content = self.client.request(url, fn['method'], headers = self.headers)
return simplejson.loads(content.decode('utf-8'))
def get_authentication_tokens(self):
"""
get_auth_url(self)
Returns an authorization URL for a user to hit.
"""
callback_url = self.callback_url or 'oob'
request_args = {}
method = 'GET'
if OAUTH_LIB_SUPPORTS_CALLBACK:
request_args['callback_url'] = callback_url
else:
# This is a hack for versions of oauth that don't support the callback URL. This is also
# done differently than the Python2 version of Twython, which uses Requests internally (as opposed to httplib2).
request_args['body'] = urllib.urlencode({'oauth_callback': callback_url})
method = 'POST'
resp, content = self.client.request(self.request_token_url, method, **request_args)
if resp['status'] != '200':
raise AuthError("Seems something couldn't be verified with your OAuth junk. Error: %s, Message: %s" % (resp['status'], content))
try:
request_tokens = dict(urllib.parse.parse_qsl(content))
except:
request_tokens = dict(cgi.parse_qsl(content))
oauth_callback_confirmed = request_tokens.get('oauth_callback_confirmed')=='true'
if not OAUTH_LIB_SUPPORTS_CALLBACK and callback_url != 'oob' and oauth_callback_confirmed:
import warnings
warnings.warn("oauth2 library doesn't support OAuth 1.0a type callback, but remote requires it")
oauth_callback_confirmed = False
auth_url_params = {
'oauth_token' : request_tokens['oauth_token'],
}
if OAUTH_CALLBACK_IN_URL or (callback_url!='oob' and not oauth_callback_confirmed):
auth_url_params['oauth_callback'] = callback_url
request_tokens['auth_url'] = self.authenticate_url + '?' + urllib.parse.urlencode(auth_url_params)
return request_tokens
def get_authorized_tokens(self):
"""
get_authorized_tokens
Returns authorized tokens after they go through the auth_url phase.
"""
resp, content = self.client.request(self.access_token_url, "GET")
try:
return dict(urllib.parse.parse_qsl(content))
except:
return dict(cgi.parse_qsl(content))
# ------------------------------------------------------------------------------------------------------------------------
# The following methods are all different in some manner or require special attention with regards to the Twitter API.
# Because of this, we keep them separate from all the other endpoint definitions - ideally this should be change-able,
# but it's not high on the priority list at the moment.
# ------------------------------------------------------------------------------------------------------------------------
@staticmethod
def constructApiURL(base_url, params):
return base_url + "?" + "&".join(["%s=%s" % (key, urllib.parse.quote_plus(Twython.unicode2utf8(value))) for (key, value) in list(params.items())])
@staticmethod
def shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl"):
"""shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl")
Shortens url specified by url_to_shorten.
Parameters:
url_to_shorten - URL to shorten.
shortener - In case you want to use a url shortening service other than is.gd.
"""
try:
content = urllib.request.urlopen(shortener + "?" + urllib.parse.urlencode({query: Twython.unicode2utf8(url_to_shorten)})).read()
return content
except HTTPError as e:
raise TwythonError("shortenURL() failed with a %s error code." % repr(e.code))
def bulkUserLookup(self, ids = None, screen_names = None, version = 1, **kwargs):
""" bulkUserLookup(self, ids = None, screen_names = None, version = 1, **kwargs)
A method to do bulk user lookups against the Twitter API. Arguments (ids (numbers) / screen_names (strings)) should be flat Arrays that
contain their respective data sets.
Statuses for the users in question will be returned inline if they exist. Requires authentication!
"""
if ids:
kwargs['user_id'] = ','.join(map(str, ids))
if screen_names:
kwargs['screen_name'] = ','.join(screen_names)
lookupURL = Twython.constructApiURL("https://api.twitter.com/%d/users/lookup.json" % version, kwargs)
try:
resp, content = self.client.request(lookupURL, "POST", headers = self.headers)
return simplejson.loads(content.decode('utf-8'))
except HTTPError as e:
raise TwythonError("bulkUserLookup() failed with a %s error code." % repr(e.code), e.code)
def search(self, **kwargs):
"""search(search_query, **kwargs)
Returns tweets that match a specified query.
Parameters:
See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters.
e.g x.search(q = "jjndf", page = '2')
"""
searchURL = Twython.constructApiURL("https://search.twitter.com/search.json", kwargs)
try:
resp, content = self.client.request(searchURL, "GET", headers = self.headers)
return simplejson.loads(content.decode('utf-8'))
except HTTPError as e:
raise TwythonError("getSearchTimeline() failed with a %s error code." % repr(e.code), e.code)
def searchTwitter(self, **kwargs):
"""use search() ,this is a fall back method to support searchTwitter()
"""
return self.search(**kwargs)
def searchGen(self, search_query, **kwargs):
"""searchGen(search_query, **kwargs)
Returns a generator of tweets that match a specified query.
Parameters:
See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters.
e.g x.searchGen("python", page="2") or
x.searchGen(search_query = "python", page = "2")
"""
searchURL = Twython.constructApiURL("https://search.twitter.com/search.json?q=%s" % Twython.unicode2utf8(search_query), kwargs)
try:
resp, content = self.client.request(searchURL, "GET", headers = self.headers)
data = simplejson.loads(content.decode('utf-8'))
except HTTPError as e:
raise TwythonError("searchGen() failed with a %s error code." % repr(e.code), e.code)
if not data['results']:
raise StopIteration
for tweet in data['results']:
yield tweet
if 'page' not in kwargs:
kwargs['page'] = '2'
else:
try:
kwargs['page'] = int(kwargs['page'])
kwargs['page'] += 1
kwargs['page'] = str(kwargs['page'])
except TypeError:
raise TwythonError("searchGen() exited because page takes str")
except e:
raise TwythonError("searchGen() failed with %s error code" %\
repr(e.code), e.code)
for tweet in self.searchGen(search_query, **kwargs):
yield tweet
def searchTwitterGen(self, search_query, **kwargs):
"""use searchGen(), this is a fallback method to support
searchTwitterGen()"""
return self.searchGen(search_query, **kwargs)
def isListMember(self, list_id, id, username, version = 1):
""" isListMember(self, list_id, id, version)
Check if a specified user (id) is a member of the list in question (list_id).
**Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists.
Parameters:
list_id - Required. The slug of the list to check against.
id - Required. The ID of the user being checked in the list.
username - User who owns the list you're checking against (username)
version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc.
"""
try:
resp, content = self.client.request("https://api.twitter.com/%d/%s/%s/members/%s.json" % (version, username, list_id, repr(id)), headers = self.headers)
return simplejson.loads(content.decode('utf-8'))
except HTTPError as e:
raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code)
def isListSubscriber(self, username, list_id, id, version = 1):
""" isListSubscriber(self, list_id, id, version)
Check if a specified user (id) is a subscriber of the list in question (list_id).
**Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists.
Parameters:
list_id - Required. The slug of the list to check against.
id - Required. The ID of the user being checked in the list.
username - Required. The username of the owner of the list that you're seeing if someone is subscribed to.
version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc.
"""
try:
resp, content = self.client.request("https://api.twitter.com/%d/%s/%s/following/%s.json" % (version, username, list_id, repr(id)), headers = self.headers)
return simplejson.loads(content.decode('utf-8'))
except HTTPError as e:
raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code)
# The following methods are apart from the other Account methods, because they rely on a whole multipart-data posting function set.
def updateProfileBackgroundImage(self, filename, tile="true", version = 1):
""" updateProfileBackgroundImage(filename, tile="true")
Updates the authenticating user's profile background image.
Parameters:
image - Required. Must be a valid GIF, JPG, or PNG image of less than 800 kilobytes in size. Images with width larger than 2048 pixels will be forceably scaled down.
tile - Optional (defaults to true). If set to true the background image will be displayed tiled. The image will not be tiled otherwise.
** Note: It's sad, but when using this method, pass the tile value as a string, e.g tile="false"
version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc.
"""
try:
files = [("image", filename, open(filename, 'rb').read())]
fields = []
content_type, body = Twython.encode_multipart_formdata(fields, files)
headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
r = urllib.request.Request("https://api.twitter.com/%d/account/update_profile_background_image.json?tile=%s" % (version, tile), body, headers)
return urllib.request.urlopen(r).read()
except HTTPError as e:
raise TwythonError("updateProfileBackgroundImage() failed with a %d error code." % e.code, e.code)
def updateProfileImage(self, filename, version = 1):
""" updateProfileImage(filename)
Updates the authenticating user's profile image (avatar).
Parameters:
image - Required. Must be a valid GIF, JPG, or PNG image of less than 700 kilobytes in size. Images with width larger than 500 pixels will be scaled down.
version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc.
"""
try:
files = [("image", filename, open(filename, 'rb').read())]
fields = []
content_type, body = Twython.encode_multipart_formdata(fields, files)
headers = {'Content-Type': content_type, 'Content-Length': str(len(body))}
r = urllib.request.Request("https://api.twitter.com/%d/account/update_profile_image.json" % version, body, headers)
return urllib.request.urlopen(r).read()
except HTTPError as e:
raise TwythonError("updateProfileImage() failed with a %d error code." % e.code, e.code)
def getProfileImageUrl(self, username, size=None, version=1):
""" getProfileImageUrl(username)
Gets the URL for the user's profile image.
Parameters:
username - Required. User name of the user you want the image url of.
size - Optional. Image size. Valid options include 'normal', 'mini' and 'bigger'. Defaults to 'normal' if not given.
version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc.
"""
url = "http://api.twitter.com/%s/users/profile_image/%s.json" % (version, username)
if size:
url = self.constructApiURL(url, {'size':size})
client = httplib2.Http()
client.follow_redirects = False
resp, content = client.request(url, 'GET')
if resp.status in (301,302,303,307):
return resp['location']
elif resp.status == 200:
return simplejson.loads(content.decode('utf-8'))
raise TwythonError("getProfileImageUrl() failed with a %d error code." % resp.status, resp.status)
@staticmethod
def encode_multipart_formdata(fields, files):
BOUNDARY = email.generator._make_boundary()
CRLF = '\r\n'
L = []
for (key, value) in fields:
L.append('--' + BOUNDARY)
L.append('Content-Disposition: form-data; name="%s"' % key)
L.append('')
L.append(value)
for (key, filename, value) in files:
L.append('--' + BOUNDARY)
L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
L.append('Content-Type: %s' % mimetypes.guess_type(filename)[0] or 'application/octet-stream')
L.append('')
L.append(value)
L.append('--' + BOUNDARY + '--')
L.append('')
body = CRLF.join(L)
content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
return content_type, body
@staticmethod
def unicode2utf8(text):
try:
if isinstance(text, str):
text = text.encode('utf-8')
except:
pass
return text
@staticmethod
def encode(text):
if isinstance(text, str):
return Twython.unicode2utf8(text)
return str(text)