2626import urllib
2727import time
2828import random
29- import urlparse
3029import hmac
3130import binascii
3231import 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+
3439try :
3540 from urlparse import parse_qs
3641 parse_qs # placate pyflakes
3944 from cgi import parse_qs
4045
4146try :
42- from hashlib import sha1
43- sha = sha1
47+ from hashlib import sha1 as sha
4448except ImportError :
4549 # hashlib was added in Python 2.5
4650 import sha
4953
5054__version__ = _version .__version__
5155
52- OAUTH_VERSION = '1.0' # Hi Blaine!
56+ OAUTH_VERSION = '1.0'
5357HTTP_METHOD = 'GET'
5458SIGNATURE_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):
97101def 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
109122def to_utf8 (s ):
110123 return to_unicode (s ).encode ('utf-8' )
111124
112125def 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
118137def 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
124149def 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
156189def 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
160196def 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
212250class 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 ]
0 commit comments