From bc37a59c05c13ae637052dbfee5eed83b31648de Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 12 Jan 2016 23:29:08 -0600 Subject: expect py3 and special-case py2 behavior previously py3 was the special case, but as we move toward the future we want the py2 codebase to be considered legacy with py3 as the default --- pyramid/compat.py | 181 ++++++++++++++-------------- pyramid/i18n.py | 14 +-- pyramid/interfaces.py | 4 +- pyramid/scripts/pserve.py | 4 +- pyramid/session.py | 4 +- pyramid/tests/test_config/test_adapters.py | 8 +- pyramid/tests/test_config/test_factories.py | 8 +- pyramid/tests/test_path.py | 8 +- pyramid/tests/test_request.py | 8 +- pyramid/tests/test_scripts/test_pserve.py | 8 +- pyramid/tests/test_traversal.py | 8 +- pyramid/tests/test_urldispatch.py | 8 +- pyramid/tests/test_util.py | 30 ++--- pyramid/traversal.py | 20 +-- pyramid/urldispatch.py | 12 +- pyramid/util.py | 8 +- 16 files changed, 167 insertions(+), 166 deletions(-) diff --git a/pyramid/compat.py b/pyramid/compat.py index e9edda359..16ddef38e 100644 --- a/pyramid/compat.py +++ b/pyramid/compat.py @@ -21,22 +21,23 @@ except ImportError: # pragma: no cover import pickle # True if we are running on Python 3. +PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - long = int -else: +if PY2: string_types = basestring, integer_types = (int, long) class_types = (type, types.ClassType) text_type = unicode binary_type = str long = long +else: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + long = int def text_(s, encoding='latin-1', errors='strict'): """ If ``s`` is an instance of ``binary_type``, return @@ -52,16 +53,16 @@ def bytes_(s, encoding='latin-1', errors='strict'): return s.encode(encoding, errors) return s -if PY3: +if PY2: def ascii_native_(s): if isinstance(s, text_type): s = s.encode('ascii') - return str(s, 'ascii', 'strict') + return str(s) else: def ascii_native_(s): if isinstance(s, text_type): s = s.encode('ascii') - return str(s) + return str(s, 'ascii', 'strict') ascii_native_.__doc__ = """ Python 3: If ``s`` is an instance of ``text_type``, return @@ -72,20 +73,20 @@ Python 2: If ``s`` is an instance of ``text_type``, return """ -if PY3: +if PY2: def native_(s, encoding='latin-1', errors='strict'): """ If ``s`` is an instance of ``text_type``, return - ``s``, otherwise return ``str(s, encoding, errors)``""" + ``s.encode(encoding, errors)``, otherwise return ``str(s)``""" if isinstance(s, text_type): - return s - return str(s, encoding, errors) + return s.encode(encoding, errors) + return str(s) else: def native_(s, encoding='latin-1', errors='strict'): """ If ``s`` is an instance of ``text_type``, return - ``s.encode(encoding, errors)``, otherwise return ``str(s)``""" + ``s``, otherwise return ``str(s, encoding, errors)``""" if isinstance(s, text_type): - return s.encode(encoding, errors) - return str(s) + return s + return str(s, encoding, errors) native_.__doc__ = """ Python 3: If ``s`` is an instance of ``text_type``, return ``s``, otherwise @@ -95,17 +96,7 @@ Python 2: If ``s`` is an instance of ``text_type``, return ``s.encode(encoding, errors)``, otherwise return ``str(s)`` """ -if PY3: - from urllib import parse - urlparse = parse - from urllib.parse import quote as url_quote - from urllib.parse import quote_plus as url_quote_plus - from urllib.parse import unquote as url_unquote - from urllib.parse import urlencode as url_encode - from urllib.request import urlopen as url_open - url_unquote_text = url_unquote - url_unquote_native = url_unquote -else: +if PY2: import urlparse from urllib import quote as url_quote from urllib import quote_plus as url_quote_plus @@ -119,22 +110,19 @@ else: def url_unquote_native(v, encoding='utf-8', errors='replace'): # pragma: no cover return native_(url_unquote_text(v, encoding, errors)) +else: + from urllib import parse + urlparse = parse + from urllib.parse import quote as url_quote + from urllib.parse import quote_plus as url_quote_plus + from urllib.parse import unquote as url_unquote + from urllib.parse import urlencode as url_encode + from urllib.request import urlopen as url_open + url_unquote_text = url_unquote + url_unquote_native = url_unquote -if PY3: # pragma: no cover - import builtins - exec_ = getattr(builtins, "exec") - - def reraise(tp, value, tb=None): - if value is None: - value = tp - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - - del builtins - -else: # pragma: no cover +if PY2: # pragma: no cover def exec_(code, globs=None, locs=None): """Execute code in a namespace.""" if globs is None: @@ -151,48 +139,61 @@ else: # pragma: no cover raise tp, value, tb """) +else: # pragma: no cover + import builtins + exec_ = getattr(builtins, "exec") + + def reraise(tp, value, tb=None): + if value is None: + value = tp + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + del builtins + -if PY3: # pragma: no cover +if PY2: # pragma: no cover def iteritems_(d): - return d.items() + return d.iteritems() def itervalues_(d): - return d.values() + return d.itervalues() def iterkeys_(d): - return d.keys() + return d.iterkeys() else: # pragma: no cover def iteritems_(d): - return d.iteritems() + return d.items() def itervalues_(d): - return d.itervalues() + return d.values() def iterkeys_(d): - return d.iterkeys() + return d.keys() -if PY3: +if PY2: + map_ = map +else: def map_(*arg): return list(map(*arg)) -else: - map_ = map -if PY3: +if PY2: def is_nonstr_iter(v): - if isinstance(v, str): - return False return hasattr(v, '__iter__') else: def is_nonstr_iter(v): + if isinstance(v, str): + return False return hasattr(v, '__iter__') -if PY3: - im_func = '__func__' - im_self = '__self__' -else: +if PY2: im_func = 'im_func' im_self = 'im_self' +else: + im_func = '__func__' + im_self = '__self__' try: import configparser @@ -204,65 +205,65 @@ try: except ImportError: from Cookie import SimpleCookie -if PY3: - from html import escape -else: +if PY2: from cgi import escape - -if PY3: - input_ = input else: - input_ = raw_input + from html import escape -if PY3: - from inspect import getfullargspec as getargspec +if PY2: + input_ = raw_input else: - from inspect import getargspec + input_ = input -if PY3: - from io import StringIO as NativeIO +if PY2: + from inspect import getargspec else: + from inspect import getfullargspec as getargspec + +if PY2: from io import BytesIO as NativeIO +else: + from io import StringIO as NativeIO # "json" is not an API; it's here to support older pyramid_debugtoolbar # versions which attempt to import it import json -if PY3: +if PY2: + def decode_path_info(path): + return path.decode('utf-8') +else: # see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before # decoding it to utf-8 def decode_path_info(path): return path.encode('latin-1').decode('utf-8') -else: - def decode_path_info(path): - return path.decode('utf-8') -if PY3: - # see PEP 3333 for why we decode the path to latin-1 - from urllib.parse import unquote_to_bytes +if PY2: + from urlparse import unquote as unquote_to_bytes def unquote_bytes_to_wsgi(bytestring): - return unquote_to_bytes(bytestring).decode('latin-1') + return unquote_to_bytes(bytestring) else: - from urlparse import unquote as unquote_to_bytes + # see PEP 3333 for why we decode the path to latin-1 + from urllib.parse import unquote_to_bytes def unquote_bytes_to_wsgi(bytestring): - return unquote_to_bytes(bytestring) + return unquote_to_bytes(bytestring).decode('latin-1') def is_bound_method(ob): return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None # support annotations and keyword-only arguments in PY3 -if PY3: # pragma: no cover - from inspect import getfullargspec as getargspec -else: +if PY2: from inspect import getargspec - -if PY3: # pragma: no cover - from itertools import zip_longest else: + from inspect import getfullargspec as getargspec + +if PY2: from itertools import izip_longest as zip_longest +else: + from itertools import zip_longest def is_unbound_method(fn): """ @@ -275,9 +276,9 @@ def is_unbound_method(fn): spec = getargspec(fn) has_self = len(spec.args) > 0 and spec.args[0] == 'self' - if PY3 and inspect.isfunction(fn) and has_self: # pragma: no cover + if PY2 and inspect.ismethod(fn): return True - elif inspect.ismethod(fn): + elif inspect.isfunction(fn) and has_self: return True return False diff --git a/pyramid/i18n.py b/pyramid/i18n.py index 458f6168d..79209d342 100644 --- a/pyramid/i18n.py +++ b/pyramid/i18n.py @@ -8,7 +8,7 @@ from translationstring import ( TranslationStringFactory, # API ) -from pyramid.compat import PY3 +from pyramid.compat import PY2 from pyramid.decorator import reify from pyramid.interfaces import ( @@ -332,10 +332,10 @@ class Translations(gettext.GNUTranslations, object): """Like ``ugettext()``, but look the message up in the specified domain. """ - if PY3: - return self._domains.get(domain, self).gettext(message) - else: + if PY2: return self._domains.get(domain, self).ugettext(message) + else: + return self._domains.get(domain, self).gettext(message) def dngettext(self, domain, singular, plural, num): """Like ``ngettext()``, but look the message up in the specified @@ -353,11 +353,11 @@ class Translations(gettext.GNUTranslations, object): """Like ``ungettext()`` but look the message up in the specified domain. """ - if PY3: - return self._domains.get(domain, self).ngettext( + if PY2: + return self._domains.get(domain, self).ungettext( singular, plural, num) else: - return self._domains.get(domain, self).ungettext( + return self._domains.get(domain, self).ngettext( singular, plural, num) class LocalizerRequestMixin(object): diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index bbdc5121d..9e5cbb6d3 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -5,7 +5,7 @@ from zope.interface import ( Interface, ) -from pyramid.compat import PY3 +from pyramid.compat import PY2 # public API interfaces @@ -311,7 +311,7 @@ class IDict(Interface): def values(): """ Return a list of values from the dictionary """ - if not PY3: + if PY2: def iterkeys(): """ Return an iterator of keys from the dictionary """ diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index 5aaaffec9..155b82bdc 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -29,7 +29,7 @@ from paste.deploy import loadserver from paste.deploy import loadapp from paste.deploy.loadwsgi import loadcontext, SERVER -from pyramid.compat import PY3 +from pyramid.compat import PY2 from pyramid.compat import WIN from pyramid.paster import setup_logging @@ -1111,7 +1111,7 @@ def cherrypy_server_runner( server = wsgiserver.CherryPyWSGIServer(bind_addr, app, server_name=server_name, **kwargs) if ssl_pem is not None: - if not PY3: + if PY2: server.ssl_certificate = server.ssl_private_key = ssl_pem else: # creates wsgiserver.ssl_builtin as side-effect diff --git a/pyramid/session.py b/pyramid/session.py index 51f9de620..b3be68705 100644 --- a/pyramid/session.py +++ b/pyramid/session.py @@ -12,7 +12,7 @@ from webob.cookies import SignedSerializer from pyramid.compat import ( pickle, - PY3, + PY2, text_, bytes_, native_, @@ -325,7 +325,7 @@ def BaseCookieSessionFactory( __len__ = manage_accessed(dict.__len__) __iter__ = manage_accessed(dict.__iter__) - if not PY3: + if PY2: iteritems = manage_accessed(dict.iteritems) itervalues = manage_accessed(dict.itervalues) iterkeys = manage_accessed(dict.iterkeys) diff --git a/pyramid/tests/test_config/test_adapters.py b/pyramid/tests/test_config/test_adapters.py index b3b7576a3..ab5d6ef61 100644 --- a/pyramid/tests/test_config/test_adapters.py +++ b/pyramid/tests/test_config/test_adapters.py @@ -1,6 +1,6 @@ import unittest -from pyramid.compat import PY3 +from pyramid.compat import PY2 from pyramid.tests.test_config import IDummy class AdaptersConfiguratorMixinTests(unittest.TestCase): @@ -219,10 +219,10 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase): def test_add_response_adapter_dottednames(self): from pyramid.interfaces import IResponse config = self._makeOne(autocommit=True) - if PY3: - str_name = 'builtins.str' - else: + if PY2: str_name = '__builtin__.str' + else: + str_name = 'builtins.str' config.add_response_adapter('pyramid.response.Response', str_name) result = config.registry.queryAdapter('foo', IResponse) self.assertTrue(result.body, b'foo') diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py index 42bb5accc..452d762f8 100644 --- a/pyramid/tests/test_config/test_factories.py +++ b/pyramid/tests/test_config/test_factories.py @@ -128,17 +128,17 @@ class TestFactoriesMixin(unittest.TestCase): def test_add_request_method_with_text_type_name(self): from pyramid.interfaces import IRequestExtensions - from pyramid.compat import text_, PY3 + from pyramid.compat import text_, PY2 from pyramid.exceptions import ConfigurationError config = self._makeOne(autocommit=True) def boomshaka(r): pass def get_bad_name(): - if PY3: # pragma: nocover - name = b'La Pe\xc3\xb1a' - else: # pragma: nocover + if PY2: name = text_(b'La Pe\xc3\xb1a', 'utf-8') + else: + name = b'La Pe\xc3\xb1a' config.add_request_method(boomshaka, name=name) diff --git a/pyramid/tests/test_path.py b/pyramid/tests/test_path.py index f85373fd9..563ece6d6 100644 --- a/pyramid/tests/test_path.py +++ b/pyramid/tests/test_path.py @@ -1,6 +1,6 @@ import unittest import os -from pyramid.compat import PY3 +from pyramid.compat import PY2 here = os.path.abspath(os.path.dirname(__file__)) @@ -376,10 +376,10 @@ class TestDottedNameResolver(unittest.TestCase): def test_zope_dottedname_style_resolve_builtin(self): typ = self._makeOne() - if PY3: - result = typ._zope_dottedname_style('builtins.str', None) - else: + if PY2: result = typ._zope_dottedname_style('__builtin__.str', None) + else: + result = typ._zope_dottedname_style('builtins.str', None) self.assertEqual(result, str) def test_zope_dottedname_style_resolve_absolute(self): diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py index c528b9174..c79c84d63 100644 --- a/pyramid/tests/test_request.py +++ b/pyramid/tests/test_request.py @@ -3,7 +3,7 @@ import unittest from pyramid import testing from pyramid.compat import ( - PY3, + PY2, text_, bytes_, native_, @@ -310,10 +310,10 @@ class TestRequest(unittest.TestCase): b'/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', 'utf-8' ) - if PY3: - body = bytes(json.dumps({'a':inp}), 'utf-16') - else: + if PY2: body = json.dumps({'a':inp}).decode('utf-8').encode('utf-16') + else: + body = bytes(json.dumps({'a':inp}), 'utf-16') request.body = body request.content_type = 'application/json; charset=utf-16' self.assertEqual(request.json_body, {'a':inp}) diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py index 2d4c4e1c0..bc21665aa 100644 --- a/pyramid/tests/test_scripts/test_pserve.py +++ b/pyramid/tests/test_scripts/test_pserve.py @@ -3,11 +3,11 @@ import os import tempfile import unittest -from pyramid.compat import PY3 -if PY3: - import builtins as __builtin__ -else: +from pyramid.compat import PY2 +if PY2: import __builtin__ +else: + import builtins as __builtin__ class TestPServeCommand(unittest.TestCase): def setUp(self): diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py index aa3f1ad16..0decd04d6 100644 --- a/pyramid/tests/test_traversal.py +++ b/pyramid/tests/test_traversal.py @@ -8,7 +8,7 @@ from pyramid.compat import ( native_, text_type, url_quote, - PY3, + PY2, ) with warnings.catch_warnings(record=True) as w: @@ -335,10 +335,10 @@ class ResourceTreeTraverserTests(unittest.TestCase): foo = DummyContext(bar, path) root = DummyContext(foo, 'root') policy = self._makeOne(root) - if PY3: - vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1') - else: + if PY2: vhm_root = b'/Qu\xc3\xa9bec' + else: + vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1') environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root) request = DummyRequest(environ, path_info=text_('/bar')) result = policy(request) diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py index 20a3a4fc8..2d20b24c3 100644 --- a/pyramid/tests/test_urldispatch.py +++ b/pyramid/tests/test_urldispatch.py @@ -2,7 +2,7 @@ import unittest from pyramid import testing from pyramid.compat import ( text_, - PY3, + PY2, ) class TestRoute(unittest.TestCase): @@ -120,10 +120,10 @@ class RoutesMapperTests(unittest.TestCase): def test___call__pathinfo_cant_be_decoded(self): from pyramid.exceptions import URLDecodeError mapper = self._makeOne() - if PY3: - path_info = b'\xff\xfe\xe6\x00'.decode('latin-1') - else: + if PY2: path_info = b'\xff\xfe\xe6\x00' + else: + path_info = b'\xff\xfe\xe6\x00'.decode('latin-1') request = self._getRequest(PATH_INFO=path_info) self.assertRaises(URLDecodeError, mapper, request) diff --git a/pyramid/tests/test_util.py b/pyramid/tests/test_util.py index 2bf6a710f..0be99e949 100644 --- a/pyramid/tests/test_util.py +++ b/pyramid/tests/test_util.py @@ -1,5 +1,5 @@ import unittest -from pyramid.compat import PY3 +from pyramid.compat import PY2 class Test_InstancePropertyHelper(unittest.TestCase): @@ -149,10 +149,10 @@ class Test_InstancePropertyHelper(unittest.TestCase): from pyramid.exceptions import ConfigurationError cls = self._getTargetClass() - if PY3: # pragma: nocover - name = b'La Pe\xc3\xb1a' - else: # pragma: nocover + if PY2: name = text_(b'La Pe\xc3\xb1a', 'utf-8') + else: + name = b'La Pe\xc3\xb1a' def make_bad_name(): cls.make_property(lambda x: 1, name=name, reify=True) @@ -431,10 +431,10 @@ class Test_object_description(unittest.TestCase): self.assertEqual(self._callFUT(('a', 'b')), "('a', 'b')") def test_set(self): - if PY3: - self.assertEqual(self._callFUT(set(['a'])), "{'a'}") - else: + if PY2: self.assertEqual(self._callFUT(set(['a'])), "set(['a'])") + else: + self.assertEqual(self._callFUT(set(['a'])), "{'a'}") def test_list(self): self.assertEqual(self._callFUT(['a']), "['a']") @@ -769,25 +769,25 @@ class TestActionInfo(unittest.TestCase): class TestCallableName(unittest.TestCase): def test_valid_ascii(self): from pyramid.util import get_callable_name - from pyramid.compat import text_, PY3 + from pyramid.compat import text_ - if PY3: # pragma: nocover - name = b'hello world' - else: # pragma: nocover + if PY2: name = text_(b'hello world', 'utf-8') + else: + name = b'hello world' self.assertEqual(get_callable_name(name), 'hello world') def test_invalid_ascii(self): from pyramid.util import get_callable_name - from pyramid.compat import text_, PY3 + from pyramid.compat import text_ from pyramid.exceptions import ConfigurationError def get_bad_name(): - if PY3: # pragma: nocover - name = b'La Pe\xc3\xb1a' - else: # pragma: nocover + if PY2: name = text_(b'La Pe\xc3\xb1a', 'utf-8') + else: + name = b'La Pe\xc3\xb1a' get_callable_name(name) diff --git a/pyramid/traversal.py b/pyramid/traversal.py index db73d13fc..963a76bb5 100644 --- a/pyramid/traversal.py +++ b/pyramid/traversal.py @@ -15,7 +15,7 @@ from pyramid.interfaces import ( ) from pyramid.compat import ( - PY3, + PY2, native_, text_, ascii_native_, @@ -575,7 +575,7 @@ the ``safe`` argument to this function. This corresponds to the """ -if PY3: +if PY2: # special-case on Python 2 for speed? unchecked def quote_path_segment(segment, safe=''): """ %s """ % quote_path_segment_doc @@ -587,9 +587,10 @@ if PY3: try: return _segment_cache[(segment, safe)] except KeyError: - if segment.__class__ not in (text_type, binary_type): - segment = str(segment) - result = url_quote(native_(segment, 'utf-8'), safe) + if segment.__class__ is text_type: #isinstance slighly slower (~15%) + result = url_quote(segment.encode('utf-8'), safe) + else: + result = url_quote(str(segment), safe) # we don't need a lock to mutate _segment_cache, as the below # will generate exactly one Python bytecode (STORE_SUBSCR) _segment_cache[(segment, safe)] = result @@ -605,15 +606,14 @@ else: try: return _segment_cache[(segment, safe)] except KeyError: - if segment.__class__ is text_type: #isinstance slighly slower (~15%) - result = url_quote(segment.encode('utf-8'), safe) - else: - result = url_quote(str(segment), safe) + if segment.__class__ not in (text_type, binary_type): + segment = str(segment) + result = url_quote(native_(segment, 'utf-8'), safe) # we don't need a lock to mutate _segment_cache, as the below # will generate exactly one Python bytecode (STORE_SUBSCR) _segment_cache[(segment, safe)] = result return result - + slash = text_('/') @implementer(ITraverser) diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py index 4a8828810..c88ad9590 100644 --- a/pyramid/urldispatch.py +++ b/pyramid/urldispatch.py @@ -7,7 +7,7 @@ from pyramid.interfaces import ( ) from pyramid.compat import ( - PY3, + PY2, native_, text_, text_type, @@ -210,14 +210,14 @@ def _compile_route(route): def generator(dict): newdict = {} for k, v in dict.items(): - if PY3: - if v.__class__ is binary_type: - # url_quote below needs a native string, not bytes on Py3 - v = v.decode('utf-8') - else: + if PY2: if v.__class__ is text_type: # url_quote below needs bytes, not unicode on Py2 v = v.encode('utf-8') + else: + if v.__class__ is binary_type: + # url_quote below needs a native string, not bytes on Py3 + v = v.decode('utf-8') if k == remainder: # a stararg argument diff --git a/pyramid/util.py b/pyramid/util.py index 8fcd84f07..0a73cedaf 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -20,7 +20,7 @@ from pyramid.compat import ( integer_types, string_types, text_, - PY3, + PY2, native_ ) @@ -310,10 +310,10 @@ def object_description(object): if isinstance(object, (bool, float, type(None))): return text_(str(object)) if isinstance(object, set): - if PY3: - return shortrepr(object, '}') - else: + if PY2: return shortrepr(object, ')') + else: + return shortrepr(object, '}') if isinstance(object, tuple): return shortrepr(object, ')') if isinstance(object, list): -- cgit v1.2.3