Skip to content

Commit 8559a1f

Browse files
OAuth 2 support :)
[ci skip]
1 parent 06922b7 commit 8559a1f

File tree

2 files changed

+68
-21
lines changed

2 files changed

+68
-21
lines changed

HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ History
1616
- Removed ``get_list_memberships``, method is Twitter API 1.0 deprecated
1717
- 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
1818
- ``endpoints.py`` now contains ``EndpointsMixin`` (rather than the previous ``api_table`` dict) for Twython, which enables Twython to use functions declared in the Mixin.
19+
- 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)
1920

2021
2.10.1 (2013-05-29)
2122
++++++++++++++++++

twython/api.py

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"""
1111

1212
import requests
13-
from requests_oauthlib import OAuth1
13+
from requests.auth import HTTPBasicAuth
14+
from requests_oauthlib import OAuth1, OAuth2
1415

1516
from . import __version__
1617
from .compat import json, urlencode, parse_qsl, quote_plus, str, is_py2
@@ -21,14 +22,19 @@
2122

2223
class Twython(EndpointsMixin, object):
2324
def __init__(self, app_key=None, app_secret=None, oauth_token=None,
24-
oauth_token_secret=None, headers=None, proxies=None,
25-
api_version='1.1', ssl_verify=True):
25+
oauth_token_secret=None, access_token=None, token_type='bearer',
26+
oauth_version=1, headers=None, proxies=None, api_version='1.1',
27+
ssl_verify=True):
2628
"""Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below).
2729
2830
:param app_key: (optional) Your applications key
2931
:param app_secret: (optional) Your applications secret key
30-
:param oauth_token: (optional) Used with oauth_token_secret to make authenticated calls
31-
:param oauth_token_secret: (optional) Used with oauth_token to make authenticated calls
32+
:param oauth_token: (optional) When using **OAuth 1**, combined with oauth_token_secret to make authenticated calls
33+
:param oauth_token_secret: (optional) When using **OAuth 1** combined with oauth_token to make authenticated calls
34+
:param access_token: (optional) When using **OAuth 2**, provide a valid access token if you have one
35+
:param token_type: (optional) When using **OAuth 2**, provide your token type. Default: bearer
36+
:param oauth_version: (optional) Choose which OAuth version to use. Default: 1
37+
3238
:param headers: (optional) Custom headers to send along with the request
3339
:param proxies: (optional) A dictionary of proxies, for example {"http":"proxy.example.org:8080", "https":"proxy.example.org:8081"}.
3440
:param ssl_verify: (optional) Turns off ssl verification when False. Useful if you have development server issues.
@@ -38,14 +44,24 @@ def __init__(self, app_key=None, app_secret=None, oauth_token=None,
3844
# API urls, OAuth urls and API version; needed for hitting that there API.
3945
self.api_version = api_version
4046
self.api_url = 'https://api.twitter.com/%s'
41-
self.request_token_url = self.api_url % 'oauth/request_token'
42-
self.access_token_url = self.api_url % 'oauth/access_token'
43-
self.authenticate_url = self.api_url % 'oauth/authenticate'
4447

4548
self.app_key = app_key
4649
self.app_secret = app_secret
4750
self.oauth_token = oauth_token
4851
self.oauth_token_secret = oauth_token_secret
52+
self.access_token = access_token
53+
54+
# OAuth 1
55+
self.request_token_url = self.api_url % 'oauth/request_token'
56+
self.access_token_url = self.api_url % 'oauth/access_token'
57+
self.authenticate_url = self.api_url % 'oauth/authenticate'
58+
59+
if self.access_token: # If they pass an access token, force OAuth 2
60+
oauth_version = 2
61+
62+
# OAuth 2
63+
if oauth_version == 2:
64+
self.request_token_url = self.api_url % 'oauth2/token'
4965

5066
req_headers = {'User-Agent': 'Twython v' + __version__}
5167
if headers:
@@ -55,14 +71,20 @@ def __init__(self, app_key=None, app_secret=None, oauth_token=None,
5571
# If no keys/tokens are passed to __init__, auth=None allows for
5672
# unauthenticated requests, although I think all v1.1 requests need auth
5773
auth = None
58-
if self.app_key is not None and self.app_secret is not None and \
59-
self.oauth_token is None and self.oauth_token_secret is None:
60-
auth = OAuth1(self.app_key, self.app_secret)
61-
62-
if self.app_key is not None and self.app_secret is not None and \
63-
self.oauth_token is not None and self.oauth_token_secret is not None:
64-
auth = OAuth1(self.app_key, self.app_secret,
65-
self.oauth_token, self.oauth_token_secret)
74+
if oauth_version == 1:
75+
# User Authentication is through OAuth 1
76+
if self.app_key is not None and self.app_secret is not None and \
77+
self.oauth_token is None and self.oauth_token_secret is None:
78+
auth = OAuth1(self.app_key, self.app_secret)
79+
80+
if self.app_key is not None and self.app_secret is not None and \
81+
self.oauth_token is not None and self.oauth_token_secret is not None:
82+
auth = OAuth1(self.app_key, self.app_secret,
83+
self.oauth_token, self.oauth_token_secret)
84+
elif oauth_version == 2 and self.access_token:
85+
# Application Authentication is through OAuth 2
86+
token = {'token_type': token_type, 'access_token': self.access_token}
87+
auth = OAuth2(self.app_key, token=token)
6688

6789
self.client = requests.Session()
6890
self.client.headers = req_headers
@@ -205,6 +227,9 @@ def get_authentication_tokens(self, callback_url=None, force_login=False, screen
205227
: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
206228
:rtype: dict
207229
"""
230+
if self.oauth_version != 1:
231+
raise TwythonError('This method can only be called when your OAuth version is 1.0.')
232+
208233
callback_url = callback_url or self.callback_url
209234
request_args = {}
210235
if callback_url:
@@ -247,18 +272,39 @@ def get_authorized_tokens(self, oauth_verifier):
247272
:rtype: dict
248273
249274
"""
275+
if self.oauth_version != 1:
276+
raise TwythonError('This method can only be called when your OAuth version is 1.0.')
277+
250278
response = self.client.get(self.access_token_url, params={'oauth_verifier': oauth_verifier})
251279
authorized_tokens = dict(parse_qsl(response.content.decode('utf-8')))
252280
if not authorized_tokens:
253281
raise TwythonError('Unable to decode authorized tokens.')
254282

255283
return authorized_tokens
256284

257-
# ------------------------------------------------------------------------------------------------------------------------
258-
# The following methods are all different in some manner or require special attention with regards to the Twitter API.
259-
# Because of this, we keep them separate from all the other endpoint definitions - ideally this should be change-able,
260-
# but it's not high on the priority list at the moment.
261-
# ------------------------------------------------------------------------------------------------------------------------
285+
def obtain_access_token(self):
286+
"""Returns an OAuth 2 access token to make OAuth 2 authenticated read-only calls.
287+
288+
:rtype: string
289+
"""
290+
if self.oauth_version != 2:
291+
raise TwythonError('This method can only be called when your OAuth version is 2.0.')
292+
293+
data = {'grant_type': 'client_credentials'}
294+
basic_auth = HTTPBasicAuth(self.app_key, self.app_secret)
295+
try:
296+
response = self.client.post(self.request_token_url,
297+
data=data, auth=basic_auth)
298+
content = response.content.decode('utf-8')
299+
try:
300+
content = content.json()
301+
except AttributeError:
302+
content = json.loads(content)
303+
access_token = content['access_token']
304+
except (ValueError, requests.exceptions.RequestException):
305+
raise TwythonAuthError('Unable to obtain OAuth 2 access token.')
306+
else:
307+
return access_token
262308

263309
@staticmethod
264310
def construct_api_url(api_url, **params):

0 commit comments

Comments
 (0)