diff --git a/custom_fixers/__init__.py b/custom_fixers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/custom_fixers/fix_file_builtin.py b/custom_fixers/fix_file_builtin.py new file mode 100644 index 00000000..3850cac8 --- /dev/null +++ b/custom_fixers/fix_file_builtin.py @@ -0,0 +1,17 @@ +from lib2to3.fixer_base import BaseFix +from lib2to3.fixer_util import is_probably_builtin +from lib2to3.pgen2 import token + + +class FixFileBuiltin(BaseFix): + + _accept_type = token.NAME + + def match(self, node): + if node.value == 'file' and is_probably_builtin(node): + return True + return False + + def transform(self, node, results): + node.value = 'open' + node.changed() diff --git a/openid/__init__.py b/openid/__init__.py index 8ecb0339..d8f7ade9 100644 --- a/openid/__init__.py +++ b/openid/__init__.py @@ -45,7 +45,7 @@ # Parse the version info try: - version_info = map(int, __version__.split('.')) + version_info = tuple(map(int, __version__.split('.'))) except ValueError: version_info = (None, None, None) else: diff --git a/openid/consumer/consumer.py b/openid/consumer/consumer.py index 91e6d75a..62d0b240 100644 --- a/openid/consumer/consumer.py +++ b/openid/consumer/consumer.py @@ -345,7 +345,7 @@ def begin(self, user_url, anonymous=False): service = disco.getNextService(self._discover) except fetchers.HTTPFetchingError, why: raise DiscoveryFailure( - 'Error fetching XRDS document: %s' % (why[0],), None) + 'Error fetching XRDS document: %s' % (why.why,), None) if service is None: raise DiscoveryFailure( @@ -646,7 +646,7 @@ def _complete_id_res(self, message, endpoint, return_to): try: return self._doIdRes(message, endpoint, return_to) except (ProtocolError, DiscoveryFailure), why: - return FailureResponse(endpoint, why[0]) + return FailureResponse(endpoint, why) def _completeInvalid(self, message, endpoint, _): mode = message.getArg(OPENID_NS, 'mode', '') @@ -663,7 +663,7 @@ def _checkReturnTo(self, message, return_to): try: self._verifyReturnToArgs(message.toPostArgs()) except ProtocolError, why: - logging.exception("Verifying return_to arguments: %s" % (why[0],)) + logging.exception("Verifying return_to arguments: %s" % (why,)) return False # Check the return_to base URL against the one in the message. @@ -770,7 +770,7 @@ def _idResCheckNonce(self, message, endpoint): try: timestamp, salt = splitNonce(nonce) except ValueError, why: - raise ProtocolError('Malformed nonce: %s' % (why[0],)) + raise ProtocolError('Malformed nonce: %s' % (why,)) if (self.store is not None and not self.store.useNonce(server_url, timestamp, salt)): @@ -1262,18 +1262,18 @@ def _requestAssociation(self, endpoint, assoc_type, session_type): try: response = self._makeKVPost(args, endpoint.server_url) except fetchers.HTTPFetchingError, why: - logging.exception('openid.associate request failed: %s' % (why[0],)) + logging.exception('openid.associate request failed: %s' % (why,)) return None try: assoc = self._extractAssociation(response, assoc_session) except KeyError, why: logging.exception('Missing required parameter in response from %s: %s' - % (endpoint.server_url, why[0])) + % (endpoint.server_url, why)) return None except ProtocolError, why: logging.exception('Protocol error parsing response from %s: %s' % ( - endpoint.server_url, why[0])) + endpoint.server_url, why)) return None else: return assoc @@ -1395,7 +1395,7 @@ def _extractAssociation(self, assoc_response, assoc_session): try: expires_in = int(expires_in_str) except ValueError, why: - raise ProtocolError('Invalid expires_in field: %s' % (why[0],)) + raise ProtocolError('Invalid expires_in field: %s' % (why,)) # OpenID 1 has funny association session behaviour. if assoc_response.isOpenID1(): @@ -1434,7 +1434,7 @@ def _extractAssociation(self, assoc_response, assoc_session): secret = assoc_session.extractSecret(assoc_response) except ValueError, why: fmt = 'Malformed response for %s session: %s' - raise ProtocolError(fmt % (assoc_session.session_type, why[0])) + raise ProtocolError(fmt % (assoc_session.session_type, why)) return Association.fromExpiresIn( expires_in, assoc_handle, secret, assoc_type) @@ -1669,9 +1669,9 @@ def htmlMarkup(self, realm, return_to=None, immediate=False, @returns: str """ - return oidutil.autoSubmitHTML(self.formMarkup(realm, + return oidutil.autoSubmitHTML(self.formMarkup(realm, return_to, - immediate, + immediate, form_tag_attrs)) def shouldSendRedirect(self): diff --git a/openid/consumer/discover.py b/openid/consumer/discover.py index a30e7872..eb732d6c 100644 --- a/openid/consumer/discover.py +++ b/openid/consumer/discover.py @@ -79,7 +79,7 @@ def supportsType(self, type_uri): I consider C{/server} endpoints to implicitly support C{/signon}. """ return ( - (type_uri in self.type_uris) or + (type_uri in self.type_uris) or (type_uri == OPENID_2_0_TYPE and self.isOPIdentifier()) ) @@ -306,7 +306,7 @@ def normalizeURL(url): try: normalized = urinorm.urinorm(url) except ValueError, why: - raise DiscoveryFailure('Normalizing identifier: %s' % (why[0],), None) + raise DiscoveryFailure('Normalizing identifier: %s' % (why,), None) else: return urlparse.urldefrag(normalized)[0] diff --git a/openid/cryptutil.py b/openid/cryptutil.py index 0ac3ce3d..78420f31 100644 --- a/openid/cryptutil.py +++ b/openid/cryptutil.py @@ -102,10 +102,15 @@ def reversed(seq): def longToBinary(l): if l == 0: return '\x00' + encoded = pickle.encode_long(l) + if not isinstance(encoded, unicode): + encoded = unicode(encoded, encoding="utf-8") - return ''.join(reversed(pickle.encode_long(l))) + return ''.join(reversed(encoded)) def binaryToLong(s): + if not isinstance(s, unicode): + s = unicode(s, encoding="utf-8") return pickle.decode_long(''.join(reversed(s))) else: # We have pycrypto diff --git a/openid/extensions/sreg.py b/openid/extensions/sreg.py index c66a8b08..af392764 100644 --- a/openid/extensions/sreg.py +++ b/openid/extensions/sreg.py @@ -159,7 +159,7 @@ def getSRegNS(message): except KeyError, why: # An alias for the string 'sreg' already exists, but it's # defined for something other than simple registration - raise SRegNamespaceError(why[0]) + raise SRegNamespaceError(why) # we know that sreg_ns_uri defined, because it's defined in the # else clause of the loop as well, so disable the warning diff --git a/openid/fetchers.py b/openid/fetchers.py index 1c119a45..e89ad9f9 100644 --- a/openid/fetchers.py +++ b/openid/fetchers.py @@ -8,6 +8,15 @@ 'HTTPError'] import urllib2 +try: + urllib_version = urllib2.__version__ +except: + # Python 3 -- the urllib2 import is rewritten by 2to3 into + # `import urllib.request, urllib.error, urllib.parse', but + # 2to3 doesn't know which of them the __version__ should be + # read from. + urllib_version = urllib.request.__version__ + import time import cStringIO import sys @@ -200,7 +209,7 @@ def fetch(self, url, body=None, headers=None): headers.setdefault( 'User-Agent', - "%s Python-urllib/%s" % (USER_AGENT, urllib2.__version__,)) + "%s Python-urllib/%s" % (USER_AGENT, urllib_version,)) req = urllib2.Request(url, data=body, headers=headers) try: diff --git a/openid/oidutil.py b/openid/oidutil.py index b109a734..ef930167 100644 --- a/openid/oidutil.py +++ b/openid/oidutil.py @@ -9,10 +9,16 @@ import binascii import sys -import urlparse import logging -from urllib import urlencode +try: + import urlparse + from urllib import urlencode +except ImportError: + # Python 3.x + import urllib.parse as urlparse + from urllib.parse import urlencode + elementtree_modules = [ 'lxml.etree', @@ -166,7 +172,7 @@ def fromBase64(s): return binascii.a2b_base64(s) except binascii.Error, why: # Convert to a common exception type - raise ValueError(why[0]) + raise ValueError(str(why)) class Symbol(object): """This class implements an object that compares equal to others @@ -185,6 +191,6 @@ def __ne__(self, other): def __hash__(self): return hash((self.__class__, self.name)) - + def __repr__(self): return '' % (self.name,) diff --git a/openid/server/server.py b/openid/server/server.py index 5d426ea9..d6d65274 100644 --- a/openid/server/server.py +++ b/openid/server/server.py @@ -441,7 +441,7 @@ def fromMessage(klass, message, op_endpoint=UNUSED): session = session_class.fromMessage(message) except ValueError, why: raise ProtocolError(message, 'Error parsing %s session: %s' % - (session_class.session_type, why[0])) + (session_class.session_type,)) assoc_type = message.getArg(OPENID_NS, 'assoc_type', 'HMAC-SHA1') if assoc_type not in session.allowed_assoc_types: @@ -1505,13 +1505,13 @@ class Server(object): associations I can make and how. @type negotiator: L{openid.association.SessionNegotiator} """ - + def __init__( self, store, op_endpoint=None, - signatoryClass=Signatory, - encoderClass=SigningEncoder, + signatoryClass=Signatory, + encoderClass=SigningEncoder, decoderClass=Decoder): """A new L{Server}. diff --git a/openid/store/sqlstore.py b/openid/store/sqlstore.py index 58c4337e..f1e7e5e9 100644 --- a/openid/store/sqlstore.py +++ b/openid/store/sqlstore.py @@ -350,7 +350,7 @@ def useNonce(self, *args, **kwargs): try: return super(SQLiteStore, self).useNonce(*args, **kwargs) except self.exceptions.OperationalError, why: - if re.match('^columns .* are not unique$', why[0]): + if re.match('^columns .* are not unique$', str(why)): return False else: raise diff --git a/openid/test/__init__.py b/openid/test/__init__.py index e69de29b..99ef6040 100644 --- a/openid/test/__init__.py +++ b/openid/test/__init__.py @@ -0,0 +1,189 @@ +import os.path +import sys +import warnings +import unittest + +test_modules = [ + 'cryptutil', + 'oidutil', + 'dh', + ] + + +def fixpath(): + try: + d = os.path.dirname(__file__) + except NameError: + d = os.path.dirname(sys.argv[0]) + parent = os.path.normpath(os.path.join(d, '..')) + if parent not in sys.path: + print ("putting %s in sys.path" % (parent,)) + sys.path.insert(0, parent) + + +def otherTests(): + suite = unittest.TestSuite() + for module_name in test_modules: + module_name = 'openid.test.' + module_name + try: + test_mod = __import__(module_name, {}, {}, [None]) + except ImportError: + print ('Failed to import test %r' % (module_name,)) + else: + suite.addTest(unittest.FunctionTestCase(test_mod.test)) + + return suite + + +def pyUnitTests(): + pyunit_module_names = [ + 'server', + 'consumer', + 'message', + 'symbol', + 'etxrd', + 'xri', + 'xrires', + 'association_response', + 'auth_request', + 'negotiation', + 'verifydisco', + 'sreg', + 'ax', + 'pape', + 'pape_draft2', + 'pape_draft5', + 'rpverify', + 'extension', + ] + + pyunit_modules = [ + __import__('openid.test.test_%s' % (name,), {}, {}, ['unused']) + for name in pyunit_module_names + ] + + try: + from openid.test import test_examples + except ImportError: + e = sys.exc_info[1] + if 'twill' in str(e): + warnings.warn("Could not import twill; skipping test_examples.") + else: + raise + else: + pyunit_modules.append(test_examples) + + # Some modules have data-driven tests, and they use custom methods + # to build the test suite: + custom_module_names = [ + 'kvform', + 'linkparse', + 'oidutil', + 'storetest', + 'test_accept', + 'test_association', + 'test_discover', + 'test_fetchers', + 'test_htmldiscover', + 'test_nonce', + 'test_openidyadis', + 'test_parsehtml', + 'test_urinorm', + 'test_yadis_discover', + 'trustroot', + ] + + loader = unittest.TestLoader() + s = unittest.TestSuite() + + for m in pyunit_modules: + s.addTest(loader.loadTestsFromModule(m)) + + for name in custom_module_names: + m = __import__('openid.test.%s' % (name,), {}, {}, ['unused']) + try: + s.addTest(m.pyUnitTests()) + except AttributeError: + # because the AttributeError doesn't actually say which + # object it was. + print ("Error loading tests from %s:" % (name,)) + raise + + return s + + +def splitDir(d, count): + # in python2.4 and above, it's easier to spell this as + # d.rsplit(os.sep, count) + for i in xrange(count): + d = os.path.dirname(d) + return d + + +def _import_djopenid(): + """Import djopenid from examples/ + + It's not in sys.path, and I don't really want to put it in sys.path. + """ + import types + thisfile = os.path.abspath(sys.modules[__name__].__file__) + topDir = splitDir(thisfile, 2) + djdir = os.path.join(topDir, 'examples', 'djopenid') + + djinit = os.path.join(djdir, '__init__.py') + + djopenid = types.ModuleType('djopenid') + if sys.version_info[0] >= 3: + with open(djinit, 'r') as f: + exec(compile(f.read(), "__init__.py"), "exec", djinit.__dict__) + else: + execfile(djinit, djopenid.__dict__) + djopenid.__file__ = djinit + + # __path__ is the magic that makes child modules of the djopenid package + # importable. New feature in python 2.3, see PEP 302. + djopenid.__path__ = [djdir] + sys.modules['djopenid'] = djopenid + + +def django_tests(): + """Runs tests from examples/djopenid. + + @returns: number of failed tests. + """ + import os + # Django uses this to find out where its settings are. + os.environ['DJANGO_SETTINGS_MODULE'] = 'djopenid.settings' + + _import_djopenid() + + try: + import django.test.simple + except ImportError: + warnings.warn("django.test.simple not found; " + "django examples not tested.") + return 0 + + import djopenid.server.models, djopenid.consumer.models + print ("Testing Django examples:") + + # These tests do get put in to a pyunit test suite, so we could run them + # with the other pyunit tests, but django also establishes a test database + # for them, so we let it do that thing instead. + return django.test.simple.run_tests([djopenid.server.models, + djopenid.consumer.models]) + +try: + bool +except NameError: + def bool(x): + return not not x + + +def test_suite(): + fixpath() + all_suite = unittest.TestSuite() + all_suite.addTests(otherTests()) + all_suite.addTests(pyUnitTests()) + all_suite.addTests(unittest.FunctionTestCase(django_tests)) + return all_suite diff --git a/openid/test/discoverdata.py b/openid/test/discoverdata.py index 78b18f73..57ef3331 100644 --- a/openid/test/discoverdata.py +++ b/openid/test/discoverdata.py @@ -87,7 +87,7 @@ def generateSample(test_name, base_url, template = getData(filename, test_name) except IOError, why: import errno - if why[0] == errno.ENOENT: + if int(why) == errno.ENOENT: raise KeyError(filename) else: raise diff --git a/openid/test/storetest.py b/openid/test/storetest.py index e428c36d..c7cd1bce 100644 --- a/openid/test/storetest.py +++ b/openid/test/storetest.py @@ -269,7 +269,7 @@ def test_mysql(): try: conn = MySQLdb.connect(user=db_user, passwd=db_passwd, host = db_host) except MySQLdb.OperationalError, why: - if why[0] == 2005: + if int(why) == 2005: print ('Skipping MySQL store test (cannot connect ' 'to test server on host %r)' % (db_host,)) return diff --git a/openid/test/test_consumer.py b/openid/test/test_consumer.py index 33a75647..c61d4763 100644 --- a/openid/test/test_consumer.py +++ b/openid/test/test_consumer.py @@ -788,7 +788,7 @@ def test(self): try: self.consumer._idResCheckForFields(message) except ProtocolError, why: - self.failUnless(why[0].startswith('Missing required')) + self.failUnless(str(why).startswith('Missing required')) else: self.fail('Expected an error, but none occurred') return test @@ -799,7 +799,7 @@ def test(self): try: self.consumer._idResCheckForFields(message) except ProtocolError, why: - self.failUnless(why[0].endswith('not signed')) + self.failUnless(str(why).endswith('not signed')) else: self.fail('Expected an error, but none occurred') return test @@ -1477,8 +1477,8 @@ def test(): try: self.consumer.begin('unused in this test') except DiscoveryFailure, why: - self.failUnless(why[0].startswith('Error fetching')) - self.failIf(why[0].find('Unit test') == -1) + self.failUnless(str(why).startswith('Error fetching')) + self.failIf(str(why).find('Unit test') == -1) else: self.fail('Expected DiscoveryFailure') @@ -1493,8 +1493,8 @@ def test(): try: self.consumer.begin(url) except DiscoveryFailure, why: - self.failUnless(why[0].startswith('No usable OpenID')) - self.failIf(why[0].find(url) == -1) + self.failUnless(str(why).startswith('No usable OpenID')) + self.failIf(str(why).find(url) == -1) else: self.fail('Expected DiscoveryFailure') @@ -1771,7 +1771,7 @@ def discoverAndVerify(claimed_id, to_match_endpoints): self.failUnless(str(e), text) else: self.fail("expected ProtocolError, %r returned." % (r,)) - + def test_foreignDelegate(self): text = "verify failed" diff --git a/openid/test/test_fetchers.py b/openid/test/test_fetchers.py index da1eea84..a119da96 100644 --- a/openid/test/test_fetchers.py +++ b/openid/test/test_fetchers.py @@ -90,7 +90,7 @@ def run_fetcher_tests(server): try: exc_fetchers.append(klass()) except RuntimeError, why: - if why[0].startswith('Cannot find %s library' % (library_name,)): + if str(why).startswith('Cannot find %s library' % (library_name,)): try: __import__(library_name) except ImportError: diff --git a/openid/test/test_parsehtml.py b/openid/test/test_parsehtml.py index 221565b2..9452686e 100644 --- a/openid/test/test_parsehtml.py +++ b/openid/test/test_parsehtml.py @@ -18,7 +18,7 @@ def runTest(self): try: p.feed(self.case) except ParseDone, why: - found = why[0] + found = str(why) # make sure we protect outselves against accidental bogus # test cases diff --git a/openid/urinorm.py b/openid/urinorm.py index 5bdbaeff..9555ea1d 100644 --- a/openid/urinorm.py +++ b/openid/urinorm.py @@ -138,7 +138,7 @@ def remove_dot_segments(path): def urinorm(uri): if isinstance(uri, unicode): - uri = _escapeme_re.sub(_pct_escape_unicode, uri).encode('ascii') + uri = _escapeme_re.sub(_pct_escape_unicode, uri).encode('ascii').decode() illegal_mo = uri_illegal_char_re.search(uri) if illegal_mo: @@ -171,7 +171,7 @@ def urinorm(uri): if '%' in host: host = host.lower() host = pct_encoded_re.sub(_pct_encoded_replace, host) - host = unicode(host, 'utf-8').encode('idna') + host = unicode(host, 'utf-8').encode('idna').decode() else: host = host.lower() diff --git a/openid/yadis/parsehtml.py b/openid/yadis/parsehtml.py index 875a089c..91deed61 100644 --- a/openid/yadis/parsehtml.py +++ b/openid/yadis/parsehtml.py @@ -185,7 +185,10 @@ def findHTMLMeta(stream): chunks.append(stream.read()) break except ParseDone, why: - uri = why[0] + if hasattr(why, 'args'): + uri = why.args[0] + else: + uri = why[0] if uri is None: # Parse finished, but we may need the rest of the file chunks.append(stream.read()) diff --git a/setup.py b/setup.py index 9e03e5fb..1e088dee 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,15 @@ except ImportError: from distutils.core import setup +# Python 3 support +extra = {} +if sys.version_info >= (3, 0): + extra.update( + use_2to3=True, + use_2to3_fixers=['custom_fixers'] + ) + + if 'sdist' in sys.argv: os.system('./admin/makedoc') @@ -41,9 +50,11 @@ "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX", "Programming Language :: Python", + "Programming Language :: Python :: 3", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Systems Administration :: Authentication/Directory", ], + **extra ) diff --git a/tox.ini b/tox.ini index 31d01e22..aed7bbb3 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py25, py26, py27, pypy +envlist = py25, py26, py27, pypy, py32 [testenv] commands = ./run_tests.sh