From e7ff4be4dc4d6746461fa8ee6809595402682def Mon Sep 17 00:00:00 2001 From: John Sirois Date: Fri, 1 Sep 2017 10:17:05 -0700 Subject: [PATCH] Kill support for python 2.6. (#408) Also introduce a `to_unicode` complement to `to_bytes` to aid in removal of `ByteIO`/`StringIO` re-directs. Closes #405 --- .travis.yml | 4 ---- docs/buildingpex.rst | 28 ++++++++++++++++++---------- pex/commands/bdist_pex.py | 4 ++-- pex/compatibility.py | 34 ++++++++++++++++++---------------- pex/http.py | 10 ++++++---- pex/interpreter.py | 8 ++------ pex/pep425.py | 2 +- pex/pex_builder.py | 6 +----- setup.py | 2 +- tests/test_compatibility.py | 18 +++++++++++++++--- tests/test_http.py | 2 +- tests/test_pex.py | 2 -- tests/test_pex_builder.py | 3 --- tox.ini | 4 ---- 14 files changed, 65 insertions(+), 62 deletions(-) diff --git a/.travis.yml b/.travis.yml index ede3e4f55..ab1e376eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,10 +18,6 @@ matrix: python: "2.7" env: TOXENV=isort-check - - language: python - python: "2.6" - env: TOXENV=py26 - - language: python python: "2.7" env: TOXENV=py27 diff --git a/docs/buildingpex.rst b/docs/buildingpex.rst index 96b615722..8d3788881 100644 --- a/docs/buildingpex.rst +++ b/docs/buildingpex.rst @@ -37,8 +37,8 @@ and invoke it. When no entry point is specified, "invocation" means starting an .. code-block:: bash $ pex - Python 2.6.9 (unknown, Jan 2 2014, 14:52:48) - [GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)] on darwin + Python 3.6.2 (default, Jul 20 2017, 03:52:27) + [GCC 7.1.1 20170630] on linux Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> @@ -51,16 +51,24 @@ absolute path of a Python binary or the name of a Python interpreter within the .. code-block:: bash - $ pex --python=python3.3 - Python 3.3.3 (default, Jan 2 2014, 14:57:01) - [GCC 4.2.1 Compatible Apple Clang 4.0 ((tags/Apple/clang-421.0.60))] on darwin + $ pex + Python 3.6.2 (default, Jul 20 2017, 03:52:27) + [GCC 7.1.1 20170630] on linux Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) - >>> print "this won't work!" + >>> print "This won't work!" File "", line 1 - print "this won't work!" + print "This won't work!" ^ - SyntaxError: invalid syntax + SyntaxError: Missing parentheses in call to 'print' + >>> + $ pex --python=python2.7 + Python 2.7.13 (default, Jul 21 2017, 03:24:34) + [GCC 7.1.1 20170630] on linux2 + Type "help", "copyright", "credits" or "license" for more information. + (InteractiveConsole) + >>> print "This works." + This works. Specifying requirements @@ -74,8 +82,8 @@ and ``psutil>1``: .. code-block:: bash $ pex flask 'psutil>1' - Python 2.6.9 (unknown, Jan 2 2014, 14:52:48) - [GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)] on darwin + Python 3.6.2 (default, Jul 20 2017, 03:52:27) + [GCC 7.1.1 20170630] on linux Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> diff --git a/pex/commands/bdist_pex.py b/pex/commands/bdist_pex.py index 7d8cbfd8a..b1f420e10 100644 --- a/pex/commands/bdist_pex.py +++ b/pex/commands/bdist_pex.py @@ -6,7 +6,7 @@ from pex.bin.pex import build_pex, configure_clp, make_relative_to_root from pex.common import die -from pex.compatibility import ConfigParser, StringIO, string +from pex.compatibility import ConfigParser, StringIO, string, to_unicode from pex.variables import ENV @@ -49,7 +49,7 @@ def split_and_strip(entry_point): if isinstance(raw_entry_points, string): parser = ConfigParser() - parser.readfp(StringIO(raw_entry_points)) + parser.readfp(StringIO(to_unicode(raw_entry_points))) if parser.has_section('console_scripts'): return dict(parser.items('console_scripts')) elif isinstance(raw_entry_points, dict): diff --git a/pex/compatibility.py b/pex/compatibility.py index bc786bf05..5697f5799 100644 --- a/pex/compatibility.py +++ b/pex/compatibility.py @@ -6,23 +6,10 @@ import os from abc import ABCMeta +from io import BytesIO, StringIO from numbers import Integral, Real from sys import version_info as sys_version_info -# TODO(wickman) Since the io package is available in 2.6.x, use that instead of -# cStringIO/StringIO -try: - # CPython 2.x - from cStringIO import StringIO -except ImportError: - try: - # Python 2.x - from StringIO import StringIO - except: - # Python 3.x - from io import StringIO - from io import BytesIO - try: # Python 2.x from ConfigParser import ConfigParser @@ -33,13 +20,12 @@ AbstractClass = ABCMeta('AbstractClass', (object,), {}) PY2 = sys_version_info[0] == 2 PY3 = sys_version_info[0] == 3 -StringIO = StringIO -BytesIO = BytesIO if PY3 else StringIO integer = (Integral,) real = (Real,) numeric = integer + real string = (str,) if PY3 else (str, unicode) +unicode_string = (str,) if PY3 else (unicode,) bytes = (bytes,) if PY2: @@ -50,6 +36,14 @@ def to_bytes(st, encoding='utf-8'): return st else: raise ValueError('Cannot convert %s to bytes' % type(st)) + + def to_unicode(st, encoding='utf-8'): + if isinstance(st, unicode): + return st + elif isinstance(st, (str, bytes)): + return unicode(st, encoding) + else: + raise ValueError('Cannot convert %s to a unicode string' % type(st)) else: def to_bytes(st, encoding='utf-8'): if isinstance(st, str): @@ -59,6 +53,14 @@ def to_bytes(st, encoding='utf-8'): else: raise ValueError('Cannot convert %s to bytes.' % type(st)) + def to_unicode(st, encoding='utf-8'): + if isinstance(st, str): + return st + elif isinstance(st, bytes): + return str(st, encoding) + else: + raise ValueError('Cannot convert %s to a unicode string' % type(st)) + _PY3_EXEC_FUNCTION = """ def exec_function(ast, globals_map): locals_map = globals_map diff --git a/pex/http.py b/pex/http.py index a1e68419f..783382e8f 100644 --- a/pex/http.py +++ b/pex/http.py @@ -37,10 +37,12 @@ else: import urllib2 as urllib_request -# This is available as hashlib.algorithms_guaranteed in >=3.2 and as -# hashlib.algorithms in >=2.7, but not available in 2.6, so we enumerate -# here. -HASHLIB_ALGORITHMS = frozenset(['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']) +try: + # The hashlib.algorithms_guaranteed function is available in >=2.7.9 and >=3.2. + HASHLIB_ALGORITHMS = frozenset(hashlib.algorithms_guaranteed) +except AttributeError: + # And the hashlib.algorithms function covers the rest of the 2.7 versions we support. + HASHLIB_ALGORITHMS = frozenset(hashlib.algorithms) class Context(AbstractClass): diff --git a/pex/interpreter.py b/pex/interpreter.py index ff9333f4f..aec6d780f 100644 --- a/pex/interpreter.py +++ b/pex/interpreter.py @@ -155,7 +155,7 @@ def hashbang(self): @property def python(self): # return the python version in the format of the 'python' key for distributions - # specifically, '2.6', '2.7', '3.2', etc. + # specifically, '2.7', '3.2', etc. return '%d.%d' % (self.version[0:2]) def __str__(self): @@ -328,7 +328,7 @@ def version_filter(version): @classmethod def sanitized_environment(cls): # N.B. This is merely a hack because sysconfig.py on the default OS X - # installation of 2.6/2.7 breaks. + # installation of 2.7 breaks. env_copy = os.environ.copy() env_copy.pop('MACOSX_DEPLOYMENT_TARGET', None) return env_copy @@ -400,10 +400,6 @@ def get_location(self, req): if req.key == dist_name and dist_version in req: return location - def supports_wheel_install(self): - """Wheel installs are broken in python 2.6""" - return self.version >= (2, 7) - def __hash__(self): return hash((self._binary, self._identity)) diff --git a/pex/pep425.py b/pex/pep425.py index a859b13d0..f4c47f12b 100644 --- a/pex/pep425.py +++ b/pex/pep425.py @@ -92,7 +92,7 @@ def _iter_supported_tags(cls, impl, version, platform): """Given a set of tags, iterate over supported tags. :param impl: Python implementation tag e.g. cp, jy, pp. - :param version: E.g. '26', '33' + :param version: E.g. '27', '33' :param platform: Platform as from :function:`pkg_resources.get_supported_platform`, for example 'linux-x86_64' or 'macosx-10.4-x86_64'. :returns: Iterator over (pyver, abi, platform) tuples. diff --git a/pex/pex_builder.py b/pex/pex_builder.py index 5f78bd97a..36b4056d5 100644 --- a/pex/pex_builder.py +++ b/pex/pex_builder.py @@ -271,11 +271,7 @@ def _add_dist_zip(self, path, dist_name): # But wheels don't have to be importable, so we need to force them # into an importable shape. We can do that by installing it into its own # wheel dir. - if not self.interpreter.supports_wheel_install(): - self._logger.warn("Wheel dependency on %s may not work correctly with Python 2.6." % - dist_name) - - if self.interpreter.supports_wheel_install() and dist_name.endswith("whl"): + if dist_name.endswith("whl"): from wheel.install import WheelFile tmp = safe_mkdtemp() whltmp = os.path.join(tmp, dist_name) diff --git a/setup.py b/setup.py index ec268d77e..9d8b1b9ae 100644 --- a/setup.py +++ b/setup.py @@ -40,12 +40,12 @@ 'Operating System :: MacOS :: MacOS X', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', ], packages = [ 'pex', diff --git a/tests/test_compatibility.py b/tests/test_compatibility.py index 0f365df63..f4379a436 100644 --- a/tests/test_compatibility.py +++ b/tests/test_compatibility.py @@ -3,7 +3,7 @@ import pytest -from pex.compatibility import to_bytes +from pex.compatibility import to_bytes, to_unicode, unicode_string def test_to_bytes(): @@ -13,6 +13,18 @@ def test_to_bytes(): assert isinstance(to_bytes(u'abc'), bytes) assert isinstance(to_bytes(b'abc'.decode('latin-1'), encoding='utf-8'), bytes) - for bad_values in (123, None): + for bad_value in (123, None): with pytest.raises(ValueError): - to_bytes(bad_values) + to_bytes(bad_value) + + +def test_to_unicode(): + assert isinstance(to_unicode(''), unicode_string) + assert isinstance(to_unicode('abc'), unicode_string) + assert isinstance(to_unicode(b'abc'), unicode_string) + assert isinstance(to_unicode(u'abc'), unicode_string) + assert isinstance(to_unicode(u'abc'.encode('latin-1'), encoding='latin-1'), unicode_string) + + for bad_value in (123, None): + with pytest.raises(ValueError): + to_unicode(bad_value) diff --git a/tests/test_http.py b/tests/test_http.py index b07f207eb..aa16ee9dc 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -120,7 +120,7 @@ def __init__(self, data): self.version = 'HTTP/1.1' self.reason = 'OK' if PY2: - self.msg = HTTPMessage(BytesIO('Content-Type: application/x-compressed\r\n')) + self.msg = HTTPMessage(BytesIO(b'Content-Type: application/x-compressed\r\n')) else: self.msg = HTTPMessage() self.msg.add_header('Content-Type', 'application/x-compressed') diff --git a/tests/test_pex.py b/tests/test_pex.py index 418b53873..22f2ef4a4 100644 --- a/tests/test_pex.py +++ b/tests/test_pex.py @@ -186,8 +186,6 @@ def test_site_libs_excludes_prefix(): assert sys.prefix not in site_libs -@pytest.mark.skipif(sys.version_info < (2, 7), - reason="wheel script installation is broken on python 2.6") @pytest.mark.parametrize('zip_safe', (False, True)) @pytest.mark.parametrize('project_name', ('my_project', 'my-project')) @pytest.mark.parametrize('installer_impl', (EggInstaller, WheelInstaller)) diff --git a/tests/test_pex_builder.py b/tests/test_pex_builder.py index af70830f9..c26cd5fcb 100644 --- a/tests/test_pex_builder.py +++ b/tests/test_pex_builder.py @@ -3,7 +3,6 @@ import os import stat -import sys import pytest from twitter.common.contextutil import temporary_dir @@ -67,8 +66,6 @@ def test_pex_builder(): assert fp.read() == 'success' -@pytest.mark.skipif(sys.version_info < (2, 7), - reason="wheel script installation is broken on python 2.6") def test_pex_builder_wheeldep(): """Repeat the pex_builder test, but this time include an import of something from a wheel that doesn't come in importable form. diff --git a/tox.ini b/tox.ini index dd80abc59..9d1f959b3 100644 --- a/tox.ini +++ b/tox.ini @@ -17,7 +17,6 @@ deps = twitter.common.testing>=0.3.1,<0.4.0 wheel==0.29.0 packaging==16.8 - py26: mock py27: mock pypy: mock run: requests @@ -135,9 +134,6 @@ commands = pex --cache-dir {envtmpdir}/buildcache wheel requests . -o dist/pex35 commands = pex --cache-dir {envtmpdir}/buildcache wheel requests . -o dist/pex36 -e pex.bin.pex:main -v # Would love if you didn't have to enumerate environments here :-\ -[testenv:py26] -[testenv:py26-requests] -[testenv:py26-requests-cachecontrol] [testenv:py27] [testenv:py27-requests] [testenv:py27-requests-cachecontrol]