Python 3 update
https://patch-diff.githubusercontent.com/raw/google/gmail-oauth2-tools/pull/25.patch

Index: python/oauth2.py
--- python/oauth2.py.orig
+++ python/oauth2.py
@@ -60,15 +60,27 @@ IMAPFE and pass it as the second argument to the AUTHE
   a AUTHENTICATE XOAUTH2 a9sha9sfs[...]9dfja929dk==
 """
 
+from __future__ import print_function
+
 import base64
 import imaplib
 import json
 from optparse import OptionParser
 import smtplib
 import sys
-import urllib
 
+try:
+  from urllib.request import urlopen
+  from urllib.parse import quote, unquote, urlencode
+except ImportError:
+  from urllib import urlopen, quote, unquote, urlencode
 
+if hasattr(__builtins__, 'raw_input'):
+  def input_str(prompt):
+    return raw_input(prompt).decode(sys.stdin.encoding)
+else:
+  input_str = input
+
 def SetupOptionParser():
   # Usage message is the module's docstring.
   parser = OptionParser(usage=__doc__)
@@ -144,12 +156,12 @@ def AccountsUrl(command):
 
 def UrlEscape(text):
   # See OAUTH 5.1 for a definition of which characters need to be escaped.
-  return urllib.quote(text, safe='~-._')
+  return quote(text, safe='~-._')
 
 
 def UrlUnescape(text):
   # See OAUTH 5.1 for a definition of which characters need to be escaped.
-  return urllib.unquote(text)
+  return unquote(text)
 
 
 def FormatUrlParams(params):
@@ -162,7 +174,7 @@ def FormatUrlParams(params):
     A URL query string version of the given parameters.
   """
   param_fragments = []
-  for param in sorted(params.iteritems(), key=lambda x: x[0]):
+  for param in sorted(iter(params.items()), key=lambda x: x[0]):
     param_fragments.append('%s=%s' % (param[0], UrlEscape(param[1])))
   return '&'.join(param_fragments)
 
@@ -211,10 +223,12 @@ def AuthorizeTokens(client_id, client_secret, authoriz
   params['grant_type'] = 'authorization_code'
   request_url = AccountsUrl('o/oauth2/token')
 
-  response = urllib.urlopen(request_url, urllib.urlencode(params)).read()
-  return json.loads(response)
+  data = urlencode(params).encode('utf-8')
 
+  response = urlopen(request_url, data).read()
+  return json.loads(response.decode('utf-8'))
 
+
 def RefreshToken(client_id, client_secret, refresh_token):
   """Obtains a new token given a refresh token.
 
@@ -235,10 +249,12 @@ def RefreshToken(client_id, client_secret, refresh_tok
   params['grant_type'] = 'refresh_token'
   request_url = AccountsUrl('o/oauth2/token')
 
-  response = urllib.urlopen(request_url, urllib.urlencode(params)).read()
-  return json.loads(response)
+  data = urlencode(params).encode('utf-8')
 
+  response = urlopen(request_url, data).read()
+  return json.loads(response.decode('utf-8'))
 
+
 def GenerateOAuth2String(username, access_token, base64_encode=True):
   """Generates an IMAP OAuth2 authentication string.
 
@@ -254,7 +270,8 @@ def GenerateOAuth2String(username, access_token, base6
   """
   auth_string = 'user=%s\1auth=Bearer %s\1\1' % (username, access_token)
   if base64_encode:
-    auth_string = base64.b64encode(auth_string)
+    auth_bytes = auth_string.encode('utf-8')
+    auth_string = base64.b64encode(auth_bytes).decode()
   return auth_string
 
 
@@ -268,10 +285,11 @@ def TestImapAuthentication(user, auth_string):
     auth_string: A valid OAuth2 string, as returned by GenerateOAuth2String.
         Must not be base64-encoded, since imaplib does its own base64-encoding.
   """
-  print
+  print()
+  auth_bytes = auth_string.encode('utf-8')
   imap_conn = imaplib.IMAP4_SSL('imap.gmail.com')
   imap_conn.debug = 4
-  imap_conn.authenticate('XOAUTH2', lambda x: auth_string)
+  imap_conn.authenticate('XOAUTH2', lambda x: auth_bytes)
   imap_conn.select('INBOX')
 
 
@@ -283,18 +301,19 @@ def TestSmtpAuthentication(user, auth_string):
     auth_string: A valid OAuth2 string, not base64-encoded, as returned by
         GenerateOAuth2String.
   """
-  print
+  print()
+  auth_bytes = auth_string.encode('utf-8')
   smtp_conn = smtplib.SMTP('smtp.gmail.com', 587)
   smtp_conn.set_debuglevel(True)
   smtp_conn.ehlo('test')
   smtp_conn.starttls()
-  smtp_conn.docmd('AUTH', 'XOAUTH2 ' + base64.b64encode(auth_string))
+  smtp_conn.docmd('AUTH', 'XOAUTH2 ' + base64.b64encode(auth_bytes).decode())
 
 
 def RequireOptions(options, *args):
   missing = [arg for arg in args if getattr(options, arg) is None]
   if missing:
-    print 'Missing options: %s' % ' '.join(missing)
+    print('Missing options: %s' % ' '.join(missing))
     sys.exit(-1)
 
 
@@ -306,27 +325,27 @@ def main(argv):
     response = RefreshToken(options.client_id, options.client_secret,
                             options.refresh_token)
     if options.quiet:
-      print response['access_token']
+      print(response['access_token'])
     else:
-      print 'Access Token: %s' % response['access_token']
-      print 'Access Token Expiration Seconds: %s' % response['expires_in']
+      print('Access Token: %s' % response['access_token'])
+      print('Access Token Expiration Seconds: %s' % response['expires_in'])
   elif options.generate_oauth2_string:
     RequireOptions(options, 'user', 'access_token')
     oauth2_string = GenerateOAuth2String(options.user, options.access_token)
     if options.quiet:
-      print oauth2_string
+      print(oauth2_string)
     else:
-      print 'OAuth2 argument:\n' + oauth2_string
+      print('OAuth2 argument:\n' + oauth2_string)
   elif options.generate_oauth2_token:
     RequireOptions(options, 'client_id', 'client_secret')
-    print 'To authorize token, visit this url and follow the directions:'
-    print '  %s' % GeneratePermissionUrl(options.client_id, options.scope)
-    authorization_code = raw_input('Enter verification code: ')
+    print('To authorize token, visit this url and follow the directions:')
+    print('  %s' % GeneratePermissionUrl(options.client_id, options.scope))
+    authorization_code = input_str('Enter verification code: ')
     response = AuthorizeTokens(options.client_id, options.client_secret,
                                 authorization_code)
-    print 'Refresh Token: %s' % response['refresh_token']
-    print 'Access Token: %s' % response['access_token']
-    print 'Access Token Expiration Seconds: %s' % response['expires_in']
+    print('Refresh Token: %s' % response['refresh_token'])
+    print('Access Token: %s' % response['access_token'])
+    print('Access Token Expiration Seconds: %s' % response['expires_in'])
   elif options.test_imap_authentication:
     RequireOptions(options, 'user', 'access_token')
     TestImapAuthentication(options.user,
@@ -339,7 +358,7 @@ def main(argv):
                              base64_encode=False))
   else:
     options_parser.print_help()
-    print 'Nothing to do, exiting.'
+    print('Nothing to do, exiting.')
     return
 
 
