Skip to content

Commit bbc3746

Browse files
jaitaiwanDaniel J Holmes (jaitaiwan)
authored andcommitted
Give lib basic py3k compatibility
I've done a basic update to the library to begin getting it to be python3 compatible. A lot of upstream projects still rely on this unfortunately and their communities are not willing to change to oauthlib. This will help to prolong the inevitable.
1 parent b909b48 commit bbc3746

File tree

3 files changed

+137
-60
lines changed

3 files changed

+137
-60
lines changed

oauth2/__init__.py

Lines changed: 129 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,16 @@
2626
import urllib
2727
import time
2828
import random
29-
import urlparse
3029
import hmac
3130
import binascii
3231
import httplib2
3332

33+
try:
34+
import urlparse
35+
except ImportError:
36+
# urlparse location changed in python 3
37+
from urllib import parse as urlparse
38+
3439
try:
3540
from urlparse import parse_qs
3641
parse_qs # placate pyflakes
@@ -39,8 +44,7 @@
3944
from cgi import parse_qs
4045

4146
try:
42-
from hashlib import sha1
43-
sha = sha1
47+
from hashlib import sha1 as sha
4448
except ImportError:
4549
# hashlib was added in Python 2.5
4650
import sha
@@ -49,7 +53,7 @@
4953

5054
__version__ = _version.__version__
5155

52-
OAUTH_VERSION = '1.0' # Hi Blaine!
56+
OAUTH_VERSION = '1.0'
5357
HTTP_METHOD = 'GET'
5458
SIGNATURE_METHOD = 'PLAINTEXT'
5559

@@ -87,7 +91,7 @@ def build_xoauth_string(url, consumer, token=None):
8791
request.sign_request(signing_method, consumer, token)
8892

8993
params = []
90-
for k, v in sorted(request.iteritems()):
94+
for k, v in sorted(request.items()):
9195
if v is not None:
9296
params.append('%s="%s"' % (k, escape(v)))
9397

@@ -97,41 +101,66 @@ def build_xoauth_string(url, consumer, token=None):
97101
def to_unicode(s):
98102
""" Convert to unicode, raise exception with instructive error
99103
message if s is not unicode, ascii, or utf-8. """
100-
if not isinstance(s, unicode):
104+
# Python 3 strings are unicode (utf-8) by default
105+
try:
106+
if not isinstance(s, unicode):
107+
if not isinstance(s, str):
108+
raise TypeError('You are required to pass either unicode or string here, not: %r (%s)' % (type(s), s))
109+
try:
110+
s = s.decode('utf-8')
111+
except UnicodeDecodeError as le:
112+
raise TypeError('You are required to pass either a unicode object or a utf-8 string here. You passed a Python string object which contained non-utf-8: %r. The UnicodeDecodeError that resulted from attempting to interpret it as utf-8 was: %s' % (s, le,))
113+
except NameError:
101114
if not isinstance(s, str):
102115
raise TypeError('You are required to pass either unicode or string here, not: %r (%s)' % (type(s), s))
103116
try:
104-
s = s.decode('utf-8')
105-
except UnicodeDecodeError, le:
117+
s = s.encode('utf-8')
118+
except UnicodeDecodeError as le:
106119
raise TypeError('You are required to pass either a unicode object or a utf-8 string here. You passed a Python string object which contained non-utf-8: %r. The UnicodeDecodeError that resulted from attempting to interpret it as utf-8 was: %s' % (s, le,))
107120
return s
108121

109122
def to_utf8(s):
110123
return to_unicode(s).encode('utf-8')
111124

112125
def to_unicode_if_string(s):
113-
if isinstance(s, basestring):
114-
return to_unicode(s)
115-
else:
116-
return s
126+
try:
127+
if isinstance(s, basestring):
128+
return to_unicode(s)
129+
else:
130+
return s
131+
except NameError:
132+
if isinstance(s, str):
133+
return to_unicode(s)
134+
else:
135+
return s
117136

118137
def to_utf8_if_string(s):
119-
if isinstance(s, basestring):
120-
return to_utf8(s)
121-
else:
122-
return s
138+
try:
139+
if isinstance(s, basestring):
140+
return to_utf8(s)
141+
else:
142+
return s
143+
except NameError:
144+
if isinstance(s, str):
145+
return to_utf8(s)
146+
else:
147+
return s
123148

124149
def to_unicode_optional_iterator(x):
125150
"""
126151
Raise TypeError if x is a str containing non-utf8 bytes or if x is
127152
an iterable which contains such a str.
128153
"""
129-
if isinstance(x, basestring):
130-
return to_unicode(x)
154+
try:
155+
if isinstance(x, basestring):
156+
return to_unicode(x)
157+
except NameError:
158+
if isinstance(x, str):
159+
return to_unicode(x)
131160

132161
try:
133162
l = list(x)
134-
except TypeError, e:
163+
except TypeError as e:
135164
assert 'is not iterable' in str(e)
136165
return x
137166
else:
@@ -142,20 +171,27 @@ def to_utf8_optional_iterator(x):
142171
Raise TypeError if x is a str or if x is an iterable which
143172
contains a str.
144173
"""
145-
if isinstance(x, basestring):
146-
return to_utf8(x)
174+
try:
175+
if isinstance(x, basestring):
176+
return to_utf8(x)
177+
except NameError:
178+
if isinstance(x, str):
179+
return to_utf8(x)
147180

148181
try:
149182
l = list(x)
150-
except TypeError, e:
183+
except TypeError as e:
151184
assert 'is not iterable' in str(e)
152185
return x
153186
else:
154187
return [ to_utf8_if_string(e) for e in l ]
155188

156189
def escape(s):
157190
"""Escape a URL including any /."""
158-
return urllib.quote(s.encode('utf-8'), safe='~')
191+
try:
192+
return urllib.quote(s.encode('utf-8'), safe='~')
193+
except AttributeError:
194+
return urlparse.quote(s.encode('utf-8'), safe='~')
159195

160196
def generate_timestamp():
161197
"""Get seconds since epoch (UTC)."""
@@ -205,8 +241,10 @@ def __init__(self, key, secret):
205241
def __str__(self):
206242
data = {'oauth_consumer_key': self.key,
207243
'oauth_consumer_secret': self.secret}
208-
209-
return urllib.urlencode(data)
244+
try:
245+
return urllib.urlencode(data)
246+
except AttributeError:
247+
return urlparse.urlencode(data)
210248

211249

212250
class Token(object):
@@ -274,7 +312,10 @@ def to_string(self):
274312

275313
if self.callback_confirmed is not None:
276314
data['oauth_callback_confirmed'] = self.callback_confirmed
277-
return urllib.urlencode(data)
315+
try:
316+
return urllib.urlencode(data)
317+
except AttributeError:
318+
return urlparse.urlencode(data)
278319

279320
@staticmethod
280321
def from_string(s):
@@ -345,7 +386,7 @@ def __init__(self, method=HTTP_METHOD, url=None, parameters=None,
345386
self.url = to_unicode(url)
346387
self.method = method
347388
if parameters is not None:
348-
for k, v in parameters.iteritems():
389+
for k, v in parameters.items():
349390
k = to_unicode(k)
350391
v = to_unicode_optional_iterator(v)
351392
self[k] = v
@@ -382,7 +423,7 @@ def _get_timestamp_nonce(self):
382423

383424
def get_nonoauth_parameters(self):
384425
"""Get any non-OAuth parameters."""
385-
return dict([(k, v) for k, v in self.iteritems()
426+
return dict([(k, v) for k, v in self.items()
386427
if not k.startswith('oauth_')])
387428

388429
def to_header(self, realm=''):
@@ -402,13 +443,16 @@ def to_header(self, realm=''):
402443
def to_postdata(self):
403444
"""Serialize as post data for a POST request."""
404445
d = {}
405-
for k, v in self.iteritems():
446+
for k, v in self.items():
406447
d[k.encode('utf-8')] = to_utf8_optional_iterator(v)
407448

408449
# tell urlencode to deal with sequence values and map them correctly
409450
# to resulting querystring. for example self["k"] = ["v1", "v2"] will
410451
# result in 'k=v1&k=v2' and not k=%5B%27v1%27%2C+%27v2%27%5D
411-
return urllib.urlencode(d, True).replace('+', '%20')
452+
try:
453+
return urllib.urlencode(d, True).replace('+', '%20')
454+
except AttributeError:
455+
return urlparse.urlencode(d, True).replace('+', '%20')
412456

413457
def to_url(self):
414458
"""Serialize as a URL for a GET request."""
@@ -430,15 +474,20 @@ def to_url(self):
430474
fragment = to_utf8(base_url.fragment)
431475
except AttributeError:
432476
# must be python <2.5
433-
scheme = to_utf8(base_url[0])
434-
netloc = to_utf8(base_url[1])
435-
path = to_utf8(base_url[2])
436-
params = to_utf8(base_url[3])
437-
fragment = to_utf8(base_url[5])
438-
439-
url = (scheme, netloc, path, params,
440-
urllib.urlencode(query, True), fragment)
441-
return urlparse.urlunparse(url)
477+
scheme = base_url[0]
478+
netloc = base_url[1]
479+
path = base_url[2]
480+
params = base_url[3]
481+
fragment = base_url[5]
482+
483+
try:
484+
url = (scheme, netloc, path, params,
485+
urllib.urlencode(query, True), fragment)
486+
return urllib.urlunparse(url)
487+
except AttributeError:
488+
url = (scheme, netloc, path, params,
489+
urlparse.urlencode(query, True), fragment)
490+
return urlparse.urlunparse(url)
442491

443492
def get_parameter(self, parameter):
444493
ret = self.get(parameter)
@@ -450,21 +499,33 @@ def get_parameter(self, parameter):
450499
def get_normalized_parameters(self):
451500
"""Return a string that contains the parameters that must be signed."""
452501
items = []
453-
for key, value in self.iteritems():
502+
for key, value in self.items():
454503
if key == 'oauth_signature':
455504
continue
456505
# 1.0a/9.1.1 states that kvp must be sorted by key, then by value,
457506
# so we unpack sequence values into multiple items for sorting.
458-
if isinstance(value, basestring):
459-
items.append((to_utf8_if_string(key), to_utf8(value)))
460-
else:
461-
try:
462-
value = list(value)
463-
except TypeError, e:
464-
assert 'is not iterable' in str(e)
465-
items.append((to_utf8_if_string(key), to_utf8_if_string(value)))
507+
try:
508+
if isinstance(value, basestring):
509+
items.append((to_utf8_if_string(key), to_utf8(value)))
466510
else:
467-
items.extend((to_utf8_if_string(key), to_utf8_if_string(item)) for item in value)
511+
try:
512+
value = list(value)
513+
except TypeError as e:
514+
assert 'is not iterable' in str(e)
515+
items.append((to_utf8_if_string(key), to_utf8_if_string(value)))
516+
else:
517+
items.extend((to_utf8_if_string(key), to_utf8_if_string(item)) for item in value)
518+
except NameError:
519+
if isinstance(value, str):
520+
items.append((to_utf8_if_string(key), to_utf8(value)))
521+
else:
522+
try:
523+
value = list(value)
524+
except TypeError as e:
525+
assert 'is not iterable' in str(e)
526+
items.append((to_utf8_if_string(key), to_utf8_if_string(value)))
527+
else:
528+
items.extend((to_utf8_if_string(key), to_utf8_if_string(item)) for item in value)
468529

469530
# Include any query string parameters from the provided URL
470531
query = urlparse.urlparse(self.url)[4]
@@ -475,6 +536,10 @@ def get_normalized_parameters(self):
475536

476537
items.sort()
477538
encoded_str = urllib.urlencode(items, True)
539+
try:
540+
encoded_str = urllib.urlencode(items)
541+
except AttributeError:
542+
encoded_str = urlparse.urlencode(items)
478543
# Encode signature parameters per Oauth Core 1.0 protocol
479544
# spec draft 7, section 3.6
480545
# (http://tools.ietf.org/html/draft-hammer-oauth-07#section-3.6)
@@ -490,7 +555,7 @@ def sign_request(self, signature_method, consumer, token):
490555
# section 4.1.1 "OAuth Consumers MUST NOT include an
491556
# oauth_body_hash parameter on requests with form-encoded
492557
# request bodies."
493-
self['oauth_body_hash'] = base64.b64encode(sha(self.body).digest())
558+
self['oauth_body_hash'] = base64.b64encode(sha(self.body.encode("utf-8")).digest())
494559

495560
if 'oauth_consumer_key' not in self:
496561
self['oauth_consumer_key'] = consumer.key
@@ -605,7 +670,10 @@ def _split_header(header):
605670
# Split key-value.
606671
param_parts = param.split('=', 1)
607672
# Remove quotes and unescape the value.
608-
params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
673+
try:
674+
params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
675+
except AttributeError:
676+
params[param_parts[0]] = urlparse.unquote(param_parts[1].strip('\"'))
609677
return params
610678

611679
@staticmethod
@@ -667,13 +735,19 @@ def request(self, uri, method="GET", body='', headers=None,
667735
parameters=parameters, body=body, is_form_encoded=is_form_encoded)
668736

669737
req.sign_request(self.method, self.consumer, self.token)
670-
671-
schema, rest = urllib.splittype(uri)
738+
739+
try:
740+
schema, rest = urllib.splittype(uri)
741+
except AttributeError:
742+
schema, rest = urlparse.splittype(uri)
672743
if rest.startswith('//'):
673744
hierpart = '//'
674745
else:
675746
hierpart = ''
676-
host, rest = urllib.splithost(rest)
747+
try:
748+
host, rest = urllib.splithost(rest)
749+
except AttributeError:
750+
host, rest = urlparse.splithost(rest)
677751

678752
realm = schema + ':' + hierpart + host
679753

@@ -844,7 +918,7 @@ def sign(self, request, consumer, token):
844918
"""Builds the base signature string."""
845919
key, raw = self.signing_base(request, consumer, token)
846920

847-
hashed = hmac.new(key, raw, sha)
921+
hashed = hmac.new(key.encode('utf-8'), raw.encode('utf-8'), sha)
848922

849923
# Calculate the digest base 64.
850924
return binascii.b2a_base64(hashed.digest())[:-1]

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env python
2+
23
from setuptools import setup
4+
from __future__ import print_function
35
import os, re
46

57
PKG='oauth2'
@@ -15,7 +17,7 @@
1517
if mo:
1618
mverstr = mo.group(1)
1719
else:
18-
print "unable to find version in %s" % (VERSIONFILE,)
20+
print ("unable to find version in %s") % (VERSIONFILE,)
1921
raise RuntimeError("if %s.py exists, it must be well-formed" % (VERSIONFILE,))
2022
AVSRE = r"^auto_build_num *= *['\"]([^'\"]*)['\"]"
2123
mo = re.search(AVSRE, verstrline, re.M)
@@ -36,5 +38,6 @@
3638
license = "MIT License",
3739
keywords="oauth",
3840
zip_safe = True,
41+
use_2to3=True,
3942
test_suite="tests",
4043
tests_require=['coverage', 'mock'])

0 commit comments

Comments
 (0)