Skip to content

Commit 13c37a6

Browse files
sashahartmaxcountryman
authored andcommitted
python 3 support
1 parent ad1f8d6 commit 13c37a6

26 files changed

+204
-163
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ build/*
22
docs/_build/*
33
tests_output/*
44
dist/*
5+
virtualenv/*
56
*.egg-info
67
*.pyc
78
*.un~

.travis.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ language: python
22
python:
33
- 2.6
44
- 2.7
5+
- 3.3
56
install:
6-
- pip install -r requirements-dev.txt --use-mirrors
7+
- "if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install -r [email protected] --use-mirrors; fi"
8+
- "if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install -r [email protected] --use-mirrors; fi"
9+
- "if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install -r [email protected] --use-mirrors; fi"
710
- pip install . --use-mirrors
811
script: make check
File renamed without changes.

CHANGELOG

+5-6
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,17 @@ Rauth Changelog
44
This provides a list of changes to rauth by release.
55

66

7-
Changes in Version 0.5.4
8-
7+
Changes in Version 0.5.5
8+
------------------------
99
* BUGFIX Fixed upstream CaseInsensitiveDict API changes
1010
* BUGFIX Corrected multiple quoting of oauth_token
1111

12-
1312
Changes in Version 0.5.4
14-
13+
------------------------
1514
* BUGFIX Corrected adding header to OAuth 2.0 where no access token existed
1615

1716
Changes in Version 0.5.3
18-
17+
------------------------
1918
* Added an ad hoc check against double signing in OAuth 1.0/a
2019

2120
Changes in Version 0.5.2
@@ -103,4 +102,4 @@ Changes in Version 0.5.0
103102

104103
* Removed examples without correct, functioning credentials
105104

106-
* Test suite completely rewritten for more robust request checking
105+
* Test suite completely rewritten for more robust request checking

MANIFEST.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
include README.markdown LICENSE CHANGELOG
1+
include README.md LICENSE CHANGELOG

Makefile

+5-11
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
all:
44
@echo 'test run the unit tests'
5-
@echo 'coverage generate coverage statistics'
6-
@echo 'pep8 check pep8 compliance'
7-
@echo 'pyflakes check for unused imports (requires pyflakes)'
5+
@echo 'flake8 check for PEP8 compliance and unused imports'
86
@echo 'check make sure you are ready to commit'
97
@echo 'clean cleanup the source tree'
108

@@ -15,14 +13,10 @@ test: clean_coverage
1513
clean_coverage:
1614
@rm -f .coverage
1715

18-
pep8:
19-
@echo 'Checking pep8 compliance...'
20-
@pep8 rauth tests
16+
flake8:
17+
@echo 'Running flake8...'
18+
@flake8 rauth tests
2119

22-
pyflakes:
23-
@echo 'Running pyflakes...'
24-
@pyflakes rauth tests
25-
26-
check: pep8 pyflakes test
20+
check: flake8 test
2721
@grep ^TOTAL tests_output/test.log | grep 100% >/dev/null || \
2822
{ echo 'Unit tests coverage is incomplete.'; exit 1; }

README.markdown renamed to README.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ twitter = OAuth1Service(
4242
request_token_url='https://api.twitter.com/oauth/request_token',
4343
access_token_url='https://api.twitter.com/oauth/access_token',
4444
authorize_url='https://api.twitter.com/oauth/authorize',
45-
base_url='https://api.twitter.com/1/')
45+
base_url='https://api.twitter.com/1.1/')
4646
```
4747

4848
Then get an OAuth 1.0 request token:
@@ -58,7 +58,7 @@ application, Twitter will give you a PIN to enter.
5858
authorize_url = twitter.get_authorize_url(request_token)
5959

6060
print 'Visit this URL in your browser: ' + authorize_url
61-
pin = raw_input('Enter PIN from browser: ')
61+
pin = raw_input('Enter PIN from browser: ') # `input` if using Python 3!
6262
```
6363

6464
Exchange the authorized request token for an authenticated `OAuth1Session`:
@@ -79,9 +79,9 @@ params = {'include_rts': 1, # Include retweets
7979
r = session.get('statuses/home_timeline.json', params=params)
8080

8181
for i, tweet in enumerate(r.json(), 1):
82-
handle = tweet['user']['screen_name'].encode('utf-8')
83-
text = tweet['text'].encode('utf-8')
84-
print '{0}. @{1} - {2}'.format(i, handle, text)
82+
handle = tweet['user']['screen_name']
83+
text = tweet['text']
84+
print(u'{0}. @{1} - {2}'.format(i, handle, text))
8585
```
8686

8787
Here's the full example: [examples/twitter-timeline-cli.py](https://github.com/litl/rauth/blob/master/examples/twitter-timeline-cli.py).
@@ -103,7 +103,8 @@ Basically there's just a few steps to getting started:
103103

104104
Note: Before you make a pull request, please run `make check`. If your code
105105
passes then you should be good to go! Requirements for running tests are in
106-
`requirements.txt`.
106+
`requirements-dev@<python-version>.txt`. You may also want to run `tox` to
107+
ensure that nothing broke in other supported environments, e.g. Python 3.
107108

108109
## Copyright and License
109110

examples/facebook-cli.py

-34
This file was deleted.

examples/twitter-timeline-cli.py

+21-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
from rauth.service import OAuth1Service
2-
1+
from rauth import OAuth1Service
2+
3+
try:
4+
read_input = raw_input
5+
except NameError:
6+
read_input = input
7+
38
# Get a real consumer key & secret from https://dev.twitter.com/apps/new
49
twitter = OAuth1Service(
510
name='twitter',
@@ -8,26 +13,26 @@
813
request_token_url='https://api.twitter.com/oauth/request_token',
914
access_token_url='https://api.twitter.com/oauth/access_token',
1015
authorize_url='https://api.twitter.com/oauth/authorize',
11-
base_url='https://api.twitter.com/1/')
12-
16+
base_url='https://api.twitter.com/1.1/')
17+
1318
request_token, request_token_secret = twitter.get_request_token()
14-
19+
1520
authorize_url = twitter.get_authorize_url(request_token)
16-
17-
print 'Visit this URL in your browser: ' + authorize_url
18-
pin = raw_input('Enter PIN from browser: ')
19-
21+
22+
print('Visit this URL in your browser: {url}'.format(url=authorize_url))
23+
pin = read_input('Enter PIN from browser: ')
24+
2025
session = twitter.get_auth_session(request_token,
2126
request_token_secret,
2227
method='POST',
2328
data={'oauth_verifier': pin})
24-
29+
2530
params = {'include_rts': 1, # Include retweets
2631
'count': 10} # 10 tweets
27-
28-
r = session.get('statuses/home_timeline.json', params=params)
29-
32+
33+
r = session.get('statuses/home_timeline.json', params=params, verify=True)
34+
3035
for i, tweet in enumerate(r.json(), 1):
31-
handle = tweet['user']['screen_name'].encode('utf-8')
32-
text = tweet['text'].encode('utf-8')
33-
print '{0}. @{1} - {2}'.format(i, handle, text)
36+
handle = tweet['user']['screen_name']
37+
text = tweet['text']
38+
print(u'{0}. @{1} - {2}'.format(i, handle, text))

rauth/compat.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# -*- coding: utf-8 -*-
2+
'''
3+
rauth.compat
4+
------------
5+
6+
A module providing tools for cross-version compatibility.
7+
'''
8+
import sys
9+
10+
11+
if sys.version_info < (3, 0): # pragma: no cover
12+
from urllib import quote, urlencode
13+
from urlparse import parse_qsl, urlsplit, urlunsplit, urljoin
14+
15+
def is_basestring(astring):
16+
return isinstance(astring, basestring) # NOQA
17+
18+
def iteritems(adict):
19+
return adict.iteritems()
20+
21+
else: # pragma: no cover
22+
from urllib.parse import (quote, urlencode, parse_qsl, urlsplit,
23+
urlunsplit, urljoin)
24+
25+
# placate pyflakes
26+
(quote, urlencode, parse_qsl, urlsplit, urlunsplit, urljoin)
27+
28+
def is_basestring(astring):
29+
return isinstance(astring, (str, bytes))
30+
31+
def iteritems(adict):
32+
return adict.items()

rauth/oauth.py

+15-12
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,20 @@
1010
import hmac
1111

1212
from hashlib import sha1
13-
from urllib import quote, urlencode
14-
from urlparse import urlsplit, urlunsplit
1513

14+
from rauth.compat import is_basestring, quote, urlencode, urlsplit, urlunsplit
1615
from rauth.utils import FORM_URLENCODED
1716

1817

1918
class SignatureMethod(object):
20-
'''A base class for signature methods providing a set of common methods.'''
21-
def _encode_utf8(self, s):
22-
if isinstance(s, unicode):
19+
'''
20+
A base class for signature methods providing a set of common methods.
21+
'''
22+
23+
def _ensure_unicode(self, s):
24+
if not isinstance(s, bytes):
2325
return s.encode('utf-8')
24-
return unicode(s, 'utf-8').encode('utf-8')
26+
return s.decode('utf-8') # pragma: no cover
2527

2628
def _escape(self, s):
2729
'''
@@ -30,7 +32,7 @@ def _escape(self, s):
3032
:param s: A string to be encoded.
3133
:type s: str
3234
'''
33-
return quote(self._encode_utf8(s), safe='~')
35+
return quote(self._ensure_unicode(s), safe='~').encode('utf-8')
3436

3537
def _remove_qs(self, url):
3638
'''
@@ -134,12 +136,12 @@ def sign(self,
134136
self._normalize_request_parameters(oauth_params, req_kwargs)
135137
parameters = map(self._escape, [method, url, oauth_params])
136138

137-
key = self._escape(consumer_secret) + '&'
139+
key = self._escape(consumer_secret) + b'&'
138140
if access_token_secret is not None:
139141
key += self._escape(access_token_secret)
140142

141143
# build a Signature Base String
142-
signature_base_string = '&'.join(parameters)
144+
signature_base_string = b'&'.join(parameters)
143145

144146
# hash the string with HMAC-SHA1
145147
hashed = hmac.new(key, signature_base_string, sha1)
@@ -164,7 +166,7 @@ def __init__(self):
164166
from Crypto.Signature import PKCS1_v1_5 as p
165167
self.RSA, self.SHA, self.PKCS1_v1_5 = r, s, p
166168
except ImportError: # pragma: no cover
167-
raise NotImplementedError("PyCrypto is required for "+self.NAME)
169+
raise NotImplementedError("PyCrypto is required for " + self.NAME)
168170

169171
def sign(self,
170172
consumer_secret,
@@ -196,16 +198,17 @@ def sign(self,
196198
parameters = map(self._escape, [method, url, oauth_params])
197199

198200
# build a Signature Base String
199-
signature_base_string = '&'.join(parameters)
201+
signature_base_string = b'&'.join(parameters)
200202

201203
# resolve the key
202-
if isinstance(consumer_secret, basestring):
204+
if is_basestring(consumer_secret):
203205
consumer_secret = self.RSA.importKey(consumer_secret)
204206
if not isinstance(consumer_secret, self.RSA._RSAobj):
205207
raise ValueError("invalid consumer_secret")
206208

207209
# hash the string with RSA-SHA1
208210
s = self.PKCS1_v1_5.new(consumer_secret)
211+
# PyCrypto SHA.new requires an encoded byte string
209212
h = self.SHA.new(signature_base_string)
210213
hashed = s.sign(h)
211214

rauth/service.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
Provides OAuth 1.0/a, 2.0 and Ofly service containers.
77
'''
88

9+
from rauth.compat import urlencode
910
from rauth.session import OAuth1Session, OAuth2Session, OflySession
1011
from rauth.utils import ENTITY_METHODS, parse_utf8_qsl
1112

12-
from urllib import urlencode
13-
1413
PROCESS_TOKEN_ERROR = ('Decoder failed to handle {key} with data as returned '
1514
'by provider. A different decoder may be needed. '
1615
'Provider returned: {raw}')
@@ -20,7 +19,7 @@ def process_token_request(r, decoder, *args):
2019
try:
2120
data = decoder(r.content)
2221
return tuple(data[key] for key in args)
23-
except KeyError, e: # pragma: no cover
22+
except KeyError as e: # pragma: no cover
2423
bad_key = e.args[0]
2524
raise KeyError(PROCESS_TOKEN_ERROR.format(key=bad_key, raw=r.content))
2625

0 commit comments

Comments
 (0)