Skip to content

Python 3 support #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 84 additions & 30 deletions oauth2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,34 @@
import urllib
import time
import random
import urlparse
try:
import urllib.parse as urlparse
from urllib.parse import quote
from urllib.parse import splithost
from urllib.parse import splittype
from urllib.parse import unquote
from urllib.parse import urlencode
except ImportError:
import urlparse
from urllib import quote
from urllib import splithost
from urllib import splittype
from urllib import unquote
from urllib import urlencode
import hmac
import binascii
import httplib2
import sys

try:
from urlparse import parse_qs
parse_qs # placate pyflakes
from urllib.parse import parse_qs
except ImportError:
# fall back for Python 2.5
from cgi import parse_qs
try:
from urlparse import parse_qs
parse_qs # placate pyflakes

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • E261 at least two spaces before inline comment

except ImportError:
# fall back for Python 2.5
from cgi import parse_qs

try:
from hashlib import sha1
Expand All @@ -52,6 +69,35 @@
OAUTH_VERSION = '1.0' # Hi Blaine!
HTTP_METHOD = 'GET'
SIGNATURE_METHOD = 'PLAINTEXT'
PY3 = sys.version > '3'


try:
basestring
except NameError:
basestring = str
try:
unicode
except NameError:
unicode = str


def b(string):
if PY3:
return string.encode('ascii')
return string


def s(bytes):
if PY3:
return bytes.decode('ascii')
return bytes


def iteritems(d):
if PY3:
return d.items()
return d.iteritems()


class Error(RuntimeError):
Expand Down Expand Up @@ -87,7 +133,7 @@ def build_xoauth_string(url, consumer, token=None):
request.sign_request(signing_method, consumer, token)

params = []
for k, v in sorted(request.iteritems()):
for k, v in sorted(iteritems(request)):
if v is not None:
params.append('%s="%s"' % (k, escape(v)))

Expand All @@ -102,7 +148,8 @@ def to_unicode(s):
raise TypeError('You are required to pass either unicode or string here, not: %r (%s)' % (type(s), s))
try:
s = s.decode('utf-8')
except UnicodeDecodeError, le:
except UnicodeDecodeError:
le = sys.exc_info()[1]
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,))
return s

Expand Down Expand Up @@ -131,7 +178,8 @@ def to_unicode_optional_iterator(x):

try:
l = list(x)
except TypeError, e:
except TypeError:
e = sys.exc_info()[1]
assert 'is not iterable' in str(e)
return x
else:
Expand All @@ -147,15 +195,18 @@ def to_utf8_optional_iterator(x):

try:
l = list(x)
except TypeError, e:
except TypeError:
e = sys.exc_info()[1]
assert 'is not iterable' in str(e)
return x
else:
return [ to_utf8_if_string(e) for e in l ]

def escape(s):
"""Escape a URL including any /."""
return urllib.quote(s.encode('utf-8'), safe='~')
if not PY3:
s = s.encode('utf-8')
return quote(s, safe='~')

def generate_timestamp():
"""Get seconds since epoch (UTC)."""
Expand Down Expand Up @@ -206,7 +257,7 @@ def __str__(self):
data = {'oauth_consumer_key': self.key,
'oauth_consumer_secret': self.secret}

return urllib.urlencode(data)
return urlencode(data)


class Token(object):
Expand Down Expand Up @@ -274,7 +325,7 @@ def to_string(self):

if self.callback_confirmed is not None:
data['oauth_callback_confirmed'] = self.callback_confirmed
return urllib.urlencode(data)
return urlencode(data)

@staticmethod
def from_string(s):
Expand Down Expand Up @@ -345,7 +396,7 @@ def __init__(self, method=HTTP_METHOD, url=None, parameters=None,
self.url = to_unicode(url)
self.method = method
if parameters is not None:
for k, v in parameters.iteritems():
for k, v in iteritems(parameters):
k = to_unicode(k)
v = to_unicode_optional_iterator(v)
self[k] = v
Expand Down Expand Up @@ -382,7 +433,7 @@ def _get_timestamp_nonce(self):

def get_nonoauth_parameters(self):
"""Get any non-OAuth parameters."""
return dict([(k, v) for k, v in self.iteritems()
return dict([(k, v) for k, v in iteritems(self)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • W291 trailing whitespace

if not k.startswith('oauth_')])

def to_header(self, realm=''):
Expand All @@ -402,13 +453,13 @@ def to_header(self, realm=''):
def to_postdata(self):
"""Serialize as post data for a POST request."""
d = {}
for k, v in self.iteritems():
for k, v in iteritems(self):
d[k.encode('utf-8')] = to_utf8_optional_iterator(v)

# tell urlencode to deal with sequence values and map them correctly
# to resulting querystring. for example self["k"] = ["v1", "v2"] will
# result in 'k=v1&k=v2' and not k=%5B%27v1%27%2C+%27v2%27%5D
return urllib.urlencode(d, True).replace('+', '%20')
return urlencode(d, True).replace('+', '%20')

def to_url(self):
"""Serialize as a URL for a GET request."""
Expand Down Expand Up @@ -437,7 +488,7 @@ def to_url(self):
fragment = to_utf8(base_url[5])

url = (scheme, netloc, path, params,
urllib.urlencode(query, True), fragment)
urlencode(query, True), fragment)
return urlparse.urlunparse(url)

def get_parameter(self, parameter):
Expand All @@ -450,7 +501,7 @@ def get_parameter(self, parameter):
def get_normalized_parameters(self):
"""Return a string that contains the parameters that must be signed."""
items = []
for key, value in self.iteritems():
for key, value in iteritems(self):
if key == 'oauth_signature':
continue
# 1.0a/9.1.1 states that kvp must be sorted by key, then by value,
Expand All @@ -460,7 +511,8 @@ def get_normalized_parameters(self):
else:
try:
value = list(value)
except TypeError, e:
except TypeError:
e = sys.exc_info()[1]
assert 'is not iterable' in str(e)
items.append((to_utf8_if_string(key), to_utf8_if_string(value)))
else:
Expand All @@ -474,7 +526,7 @@ def get_normalized_parameters(self):
items.extend(url_items)

items.sort()
encoded_str = urllib.urlencode(items, True)
encoded_str = urlencode(items, True)
# Encode signature parameters per Oauth Core 1.0 protocol
# spec draft 7, section 3.6
# (http://tools.ietf.org/html/draft-hammer-oauth-07#section-3.6)
Expand All @@ -490,7 +542,7 @@ def sign_request(self, signature_method, consumer, token):
# section 4.1.1 "OAuth Consumers MUST NOT include an
# oauth_body_hash parameter on requests with form-encoded
# request bodies."
self['oauth_body_hash'] = base64.b64encode(sha(self.body).digest())
self['oauth_body_hash'] = s(base64.b64encode(sha(b(self.body)).digest()))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • E501 line too long (85 > 79 characters)


if 'oauth_consumer_key' not in self:
self['oauth_consumer_key'] = consumer.key
Expand Down Expand Up @@ -605,18 +657,20 @@ def _split_header(header):
# Split key-value.
param_parts = param.split('=', 1)
# Remove quotes and unescape the value.
params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
params[param_parts[0]] = unquote(param_parts[1].strip('\"'))
return params

@staticmethod
def _split_url_string(param_str):
"""Turn URL string into parameters."""
parameters = parse_qs(param_str.encode('utf-8'), keep_blank_values=True)
for k, v in parameters.iteritems():
if not PY3:
param_str = param_str.encode('utf8')
parameters = parse_qs(param_str, keep_blank_values=True)
for k, v in iteritems(parameters):
if len(v) == 1:
parameters[k] = urllib.unquote(v[0])
parameters[k] = unquote(v[0])
else:
parameters[k] = sorted([urllib.unquote(s) for s in v])
parameters[k] = sorted([unquote(s) for s in v])
return parameters


Expand Down Expand Up @@ -668,12 +722,12 @@ def request(self, uri, method="GET", body='', headers=None,

req.sign_request(self.method, self.consumer, self.token)

schema, rest = urllib.splittype(uri)
schema, rest = splittype(uri)
if rest.startswith('//'):
hierpart = '//'
else:
hierpart = ''
host, rest = urllib.splithost(rest)
host, rest = splithost(rest)

realm = schema + ':' + hierpart + host

Expand Down Expand Up @@ -844,10 +898,10 @@ def sign(self, request, consumer, token):
"""Builds the base signature string."""
key, raw = self.signing_base(request, consumer, token)

hashed = hmac.new(key, raw, sha)
hashed = hmac.new(b(key), b(raw), sha)

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


class SignatureMethod_PLAINTEXT(SignatureMethod):
Expand Down
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python
from setuptools import setup
import os, re
import os, re, sys

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • E401 multiple imports on one line


from setuptools import setup, find_packages

PKG='oauth2'
VERSIONFILE = os.path.join('oauth2', '_version.py')
Expand All @@ -15,7 +16,7 @@
if mo:
mverstr = mo.group(1)
else:
print "unable to find version in %s" % (VERSIONFILE,)
sys.stdout.write("unable to find version in %s\n" % (VERSIONFILE,))
raise RuntimeError("if %s.py exists, it must be well-formed" % (VERSIONFILE,))
AVSRE = r"^auto_build_num *= *['\"]([^'\"]*)['\"]"
mo = re.search(AVSRE, verstrline, re.M)
Expand Down
Loading