Repository: tornadoweb/tornado
Pull request #2116 (2017-07-23 18:33:13)

Drop support for python 3.3, 3.5.[01]


Patch:
diff --git a/.travis.yml b/.travis.yml
index d2b54921..cae048f8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,15 +1,21 @@
 # https://travis-ci.org/tornadoweb/tornado
+dist: trusty
+
+# Use containers instead of full VMs for faster startup.
+sudo: false
+
+matrix:
+    fast_finish: true
+
 language: python
 python:
-    - 2.7.8
     - 2.7
-    - pypy
-    - 3.3
+    - pypy-5.7.1
     - 3.4
     - 3.5
     - 3.6
     - nightly
-    - pypy3
+    - pypy3.5-5.7.1-beta
 
 install:
     - if [[ $TRAVIS_PYTHON_VERSION == 2* ]]; then travis_retry pip install futures mock monotonic trollius; fi
@@ -19,17 +25,16 @@ install:
     - if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then travis_retry pip install pycurl; fi
     # Twisted runs on 2.x and 3.3+, but is flaky on pypy.
     - if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then travis_retry pip install Twisted; fi
-    - if [[ $TRAVIS_PYTHON_VERSION == '2.7' || $TRAVIS_PYTHON_VERSION == '3.5' || $TRAVIS_PYTHON_VERSION == '3.6' ]]; then travis_retry pip install sphinx sphinx_rtd_theme; fi
+    - if [[ $TRAVIS_PYTHON_VERSION == '2.7' || $TRAVIS_PYTHON_VERSION == '3.6' ]]; then travis_retry pip install sphinx sphinx_rtd_theme; fi
     # On travis the extension should always be built
     - if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then export TORNADO_EXTENSION=1; fi
     - travis_retry python setup.py install
     - travis_retry pip install codecov virtualenv
     # Create a separate no-dependencies virtualenv to make sure all imports
-    # of optional-dependencies are guarded. (skipped on pypy3 because
-    # virtualenv no longer supports py32)
-    - if [[ $TRAVIS_PYTHON_VERSION != 'pypy3' ]]; then virtualenv ./nodeps; fi
-    - if [[ $TRAVIS_PYTHON_VERSION != 'pypy3' ]]; then ./nodeps/bin/python -VV; fi
-    - if [[ $TRAVIS_PYTHON_VERSION != 'pypy3' ]]; then ./nodeps/bin/python setup.py install; fi
+    # of optional-dependencies are guarded.
+    - virtualenv ./nodeps
+    - ./nodeps/bin/python -VV
+    - ./nodeps/bin/python setup.py install
     - curl-config --version; pip freeze
 
 script:
@@ -71,17 +76,9 @@ script:
     # make coverage reports for Codecov to find
     - if [[ $TRAVIS_PYTHON_VERSION != nightly ]]; then coverage xml; fi
     - export TORNADO_EXTENSION=0
-    - if [[ $TRAVIS_PYTHON_VERSION == '3.5' || $TRAVIS_PYTHON_VERSION == 3.6 ]]; then cd ../docs && mkdir sphinx-out && sphinx-build -E -n -W -b html . sphinx-out; fi
-    - if [[ $TRAVIS_PYTHON_VERSION == '2.7' || $TRAVIS_PYTHON_VERSION == '3.5' || $TRAVIS_PYTHON_VERSION == 3.6 ]]; then cd ../docs && mkdir sphinx-doctest-out && sphinx-build -E -n -b doctest . sphinx-out; fi
+    - if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then cd ../docs && mkdir sphinx-out && sphinx-build -E -n -W -b html . sphinx-out; fi
+    - if [[ $TRAVIS_PYTHON_VERSION == '2.7' || $TRAVIS_PYTHON_VERSION == 3.6 ]]; then cd ../docs && mkdir sphinx-doctest-out && sphinx-build -E -n -b doctest . sphinx-out; fi
 
 after_success:
     # call codecov from project root
     - if [[ $TRAVIS_PYTHON_VERSION != nightly ]]; then cd ../ && codecov; fi
-
-# This reportedly works around an issue downloading packages from pypi on
-# travis.  Consider removing this after the underlying issue is fixed.
-# https://github.com/travis-ci/travis-ci/issues/2389
-sudo: false
-
-matrix:
-    fast_finish: true
diff --git a/docs/index.rst b/docs/index.rst
index 9905fe7d..a5f166a3 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -66,7 +66,7 @@ installed in this way, so you may wish to download a copy of the
 source tarball or clone the `git repository
 <https://github.com/tornadoweb/tornado>`_ as well.
 
-**Prerequisites**: Tornado runs on Python 2.7, and 3.3+
+**Prerequisites**: Tornado runs on Python 2.7, and 3.4+
 For Python 2, version 2.7.9 or newer is *strongly*
 recommended for the improved SSL support. In addition to the requirements
 which will be installed automatically by ``pip`` or ``setup.py install``,
@@ -86,7 +86,7 @@ the following optional packages may be useful:
 * `monotonic <https://pypi.python.org/pypi/monotonic>`_ or `Monotime
   <https://pypi.python.org/pypi/Monotime>`_ add support for a
   monotonic clock, which improves reliability in environments where
-  clock adjustements are frequent. No longer needed in Python 3.3.
+  clock adjustments are frequent. No longer needed in Python 3.
 
 **Platforms**: Tornado should run on any Unix-like platform, although
 for the best performance and scalability only Linux (with ``epoll``)
diff --git a/maint/vm/windows/bootstrap.py b/maint/vm/windows/bootstrap.py
index 3f12aedb..c69dc286 100644
--- a/maint/vm/windows/bootstrap.py
+++ b/maint/vm/windows/bootstrap.py
@@ -29,7 +29,7 @@ TMPDIR = r'c:\tornado_bootstrap'
 
 PYTHON_VERSIONS = [
     (r'c:\python27\python.exe', 'http://www.python.org/ftp/python/2.7.3/python-2.7.3.msi'),
-    (r'c:\python33\python.exe', 'http://www.python.org/ftp/python/3.3.0/python-3.3.0.msi'),
+    (r'c:\python36\python.exe', 'http://www.python.org/ftp/python/3.6.0/python-3.6.0.msi'),
     ]
 
 SCRIPTS_DIR = r'c:\python27\scripts'
diff --git a/maint/vm/windows/tox.ini b/maint/vm/windows/tox.ini
index 7a7ced31..c3b1725c 100644
--- a/maint/vm/windows/tox.ini
+++ b/maint/vm/windows/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py27-full, py27, py33, py27-opt, py33-monotonic
+envlist = py27-full, py27, py36, py27-opt, py36-monotonic
 setupdir = e:\
 toxworkdir = c:\tox-tornado
 
@@ -12,12 +12,13 @@ deps =
      futures
      mock
 
-[testenv:py33]
+[testenv:py36]
+# TODO: still needed?
 # tox's path mappings haven't been updated for py33 yet.
-basepython = c:\python33\python.exe
+basepython = c:\python36\python.exe
 
-[testenv:py33-monotonic]
-basepython = c:\python33\python.exe
+[testenv:py36-monotonic]
+basepython = c:\python36\python.exe
 commands = python -m tornado.test.runtests --ioloop_time_monotonic {posargs:}
 
 [testenv:py27-opt]
diff --git a/setup.py b/setup.py
index 4a94ed72..72569a1f 100644
--- a/setup.py
+++ b/setup.py
@@ -176,7 +176,6 @@ setup(
         'Programming Language :: Python :: 2',
         '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',
diff --git a/tornado/autoreload.py b/tornado/autoreload.py
index da1b100d..64204455 100644
--- a/tornado/autoreload.py
+++ b/tornado/autoreload.py
@@ -136,7 +136,7 @@ def wait():
     the command-line interface in `main`)
     """
     io_loop = ioloop.IOLoop()
-    start(io_loop)
+    io_loop.add_callback(start)
     io_loop.start()
 
 
diff --git a/tornado/curl_httpclient.py b/tornado/curl_httpclient.py
index 9a0402b5..e0ec3fba 100644
--- a/tornado/curl_httpclient.py
+++ b/tornado/curl_httpclient.py
@@ -261,6 +261,7 @@ class CurlAsyncHTTPClient(AsyncHTTPClient):
             queue=info["curl_start_time"] - info["request"].start_time,
             namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),
             connect=curl.getinfo(pycurl.CONNECT_TIME),
+            appconnect=curl.getinfo(pycurl.APPCONNECT_TIME),
             pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),
             starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),
             total=curl.getinfo(pycurl.TOTAL_TIME),
diff --git a/tornado/gen.py b/tornado/gen.py
index 5558f986..62fea303 100644
--- a/tornado/gen.py
+++ b/tornado/gen.py
@@ -478,9 +478,8 @@ class WaitIterator(object):
         self.current_future = done
         self.current_index = self._unfinished.pop(done)
 
-    @coroutine
     def __aiter__(self):
-        raise Return(self)
+        return self
 
     def __anext__(self):
         if self.done():
diff --git a/tornado/httputil.py b/tornado/httputil.py
index a98273ae..f2b289b3 100755
--- a/tornado/httputil.py
+++ b/tornado/httputil.py
@@ -467,8 +467,7 @@ class HTTPServerRequest(object):
     def __repr__(self):
         attrs = ("protocol", "host", "method", "uri", "version", "remote_ip")
         args = ", ".join(["%s=%r" % (n, getattr(self, n)) for n in attrs])
-        return "%s(%s, headers=%s)" % (
-            self.__class__.__name__, args, dict(self.headers))
+        return "%s(%s)" % (self.__class__.__name__, args)
 
 
 class HTTPInputError(Exception):
diff --git a/tornado/netutil.py b/tornado/netutil.py
index e58db35d..c054f625 100644
--- a/tornado/netutil.py
+++ b/tornado/netutil.py
@@ -66,18 +66,6 @@ if hasattr(ssl, 'SSLContext'):
             ssl.Purpose.SERVER_AUTH)
         _server_ssl_defaults = ssl.create_default_context(
             ssl.Purpose.CLIENT_AUTH)
-    else:
-        # Python 3.2-3.3
-        _client_ssl_defaults = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
-        _client_ssl_defaults.verify_mode = ssl.CERT_REQUIRED
-        _client_ssl_defaults.load_verify_locations(certifi.where())
-        _server_ssl_defaults = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
-        if hasattr(ssl, 'OP_NO_COMPRESSION'):
-            # Disable TLS compression to avoid CRIME and related attacks.
-            # This constant wasn't added until python 3.3.
-            _client_ssl_defaults.options |= ssl.OP_NO_COMPRESSION
-            _server_ssl_defaults.options |= ssl.OP_NO_COMPRESSION
-
 elif ssl:
     # Python 2.6-2.7.8
     _client_ssl_defaults = dict(cert_reqs=ssl.CERT_REQUIRED,
diff --git a/tornado/platform/asyncio.py b/tornado/platform/asyncio.py
index 830ee1f3..9d4d4f89 100644
--- a/tornado/platform/asyncio.py
+++ b/tornado/platform/asyncio.py
@@ -3,9 +3,8 @@
 .. versionadded:: 3.2
 
 This module integrates Tornado with the ``asyncio`` module introduced
-in Python 3.4 (and available `as a separate download
-<https://pypi.python.org/pypi/asyncio>`_ for Python 3.3).  This makes
-it possible to combine the two libraries on the same event loop.
+in Python 3.4. This makes it possible to combine the two libraries on
+the same event loop.
 
 Most applications should use `AsyncIOMainLoop` to run Tornado on the
 default ``asyncio`` event loop.  Applications that need to run event
diff --git a/tornado/queues.py b/tornado/queues.py
index 73589611..e6a99b15 100644
--- a/tornado/queues.py
+++ b/tornado/queues.py
@@ -263,7 +263,6 @@ class Queue(object):
         """
         return self._finished.wait(timeout)
 
-    @gen.coroutine
     def __aiter__(self):
         return _QueueIterator(self)
 
diff --git a/tox.ini b/tox.ini
index 409f025e..11ba0572 100644
--- a/tox.ini
+++ b/tox.ini
@@ -15,8 +15,8 @@ envlist =
         # Basic configurations: Run the tests in both minimal installations
         # and with all optional dependencies.
         # (pypy3 doesn't have any optional deps yet)
-        {py27,pypy,py33,py34,py35,py36,pypy3},
-        {py27,pypy,py33,py34,py35,py36}-full,
+        {py27,pypy,py34,py35,py36,pypy3},
+        {py27,pypy,py34,py35,py36}-full,
 
         # Also run the tests with each possible replacement of a default
         # component.  Run each test on both python 2 and 3 where possible.
@@ -54,7 +54,6 @@ envlist =
 # defaults for the others.
 basepython =
            py27: python2.7
-           py33: python3.3
            py34: python3.4
            py35: python3.5
            py36: python3.6
@@ -70,20 +69,18 @@ deps =
      py3-unittest2: unittest2py3k
      # cpython-only deps: pycurl installs but curl_httpclient doesn't work;
      # twisted mostly works but is a bit flaky under pypy.
-     {py27,py33,py34,py35,py36}-full: pycurl
+     {py27,py34,py35,py36}-full: pycurl
      {py2,py3}: pycurl>=7.19.3.1
      # twisted is cpython only.
-     {py27,py33,py34,py35,py36}-full: twisted
+     {py27,py34,py35,py36}-full: twisted
      {py2,py3}: twisted
-     # pycares installation currently fails on py33
-     # (https://github.com/pypa/pip/pull/816)
-     {py2,py3,py27,py33,py34,py35,py36}-full: pycares
+     {py2,py3,py27,py34,py35,py36}-full: pycares
      # futures became standard in py32
      {py2,py27,pypy}-full: futures
      # mock became standard in py33
-     {py2,py27,pypy,py3,pypy3}-full: mock
+     {py2,py27,pypy,pypy3}-full: mock
      # singledispatch became standard in py34.
-     {py2,py27,pypy,py3,py33}-full: singledispatch
+     {py2,py27,pypy}-full: singledispatch
      trollius: trollius
      py2-monotonic: monotonic
      sphinx: sphinx
@@ -91,7 +88,7 @@ deps =
 
 setenv =
        # The extension is mandatory on cpython.
-       {py2,py27,py3,py33,py34,py35,py36}: TORNADO_EXTENSION=1
+       {py2,py27,py3,py34,py35,py36}: TORNADO_EXTENSION=1
        # In python 3, opening files in text mode uses a
        # system-dependent encoding by default.  Run the tests with "C"
        # (ascii) and "utf-8" locales to ensure we don't have hidden
@@ -100,7 +97,7 @@ setenv =
        lang_utf8: LANG=en_US.utf-8
        # tox's parser chokes if all the setenv entries are conditional.
        DUMMY=dummy
-       {py2,py27,py3,py33,py34,py35,py36}-no-ext: TORNADO_EXTENSION=0
+       {py2,py27,py3,py34,py35,py36}-no-ext: TORNADO_EXTENSION=0
 
 # All non-comment lines but the last must end in a backslash.
 # Tox filters line-by-line based on the environment name.
@@ -108,7 +105,7 @@ commands =
          python \
          # py3*: -b turns on an extra warning when calling
          # str(bytes), and -bb makes it an error.
-         {py3,py33,py34,py35,py36,pypy3}: -bb \
+         {py3,py34,py35,py36,pypy3}: -bb \
          # Python's optimized mode disables the assert statement, so
          # run the tests in this mode to ensure we haven't fallen into
          # the trap of relying on an assertion's side effects or using


Test patch:
diff --git a/maint/test/cython/tox.ini b/maint/test/cython/tox.ini
index 0403df13..725e1704 100644
--- a/maint/test/cython/tox.ini
+++ b/maint/test/cython/tox.ini
@@ -1,6 +1,6 @@
 [tox]
 # This currently segfaults on pypy.
-envlist = py27,py33,py34,py35,py36
+envlist = py27,py34,py35,py36
 
 [testenv]
 deps =
@@ -13,7 +13,6 @@ commands = python -m unittest cythonapp_test
 # defaults for the others.
 basepython =
            py27: python2.7
-           py33: python3.3
            py34: python3.4
            py35: python3.5
            py36: python3.6
diff --git a/tornado/test/httputil_test.py b/tornado/test/httputil_test.py
index 7ac79925..1521ca03 100644
--- a/tornado/test/httputil_test.py
+++ b/tornado/test/httputil_test.py
@@ -418,6 +418,10 @@ class HTTPServerRequestTest(unittest.TestCase):
         requets = HTTPServerRequest(uri='/')
         self.assertIsInstance(requets.body, bytes)
 
+    def test_repr_does_not_contain_headers(self):
+        request = HTTPServerRequest(uri='/', headers={'Canary': 'Coal Mine'})
+        self.assertTrue('Canary' not in repr(request))
+
 
 class ParseRequestStartLineTest(unittest.TestCase):
     METHOD = "GET"
diff --git a/tornado/test/iostream_test.py b/tornado/test/iostream_test.py
index 03016447..751dfe2e 100644
--- a/tornado/test/iostream_test.py
+++ b/tornado/test/iostream_test.py
@@ -9,7 +9,7 @@ from tornado.netutil import ssl_wrap_socket
 from tornado.stack_context import NullContext
 from tornado.tcpserver import TCPServer
 from tornado.testing import AsyncHTTPTestCase, AsyncHTTPSTestCase, AsyncTestCase, bind_unused_port, ExpectLog, gen_test
-from tornado.test.util import unittest, skipIfNonUnix, refusing_port
+from tornado.test.util import unittest, skipIfNonUnix, refusing_port, skipPypy3V58
 from tornado.web import RequestHandler, Application
 import errno
 import logging
@@ -538,6 +538,7 @@ class TestIOStreamMixin(object):
             client.close()
 
     @skipIfNonUnix
+    @skipPypy3V58
     def test_inline_read_error(self):
         # An error on an inline read is raised without logging (on the
         # assumption that it will eventually be noticed or logged further
@@ -556,6 +557,7 @@ class TestIOStreamMixin(object):
             server.close()
             client.close()
 
+    @skipPypy3V58
     def test_async_read_error_logging(self):
         # Socket errors on asynchronous reads should be logged (but only
         # once).
@@ -991,7 +993,7 @@ class TestIOStreamStartTLS(AsyncTestCase):
         server_future = self.server_start_tls(_server_ssl_options())
         client_future = self.client_start_tls(
             ssl.create_default_context(),
-            server_hostname=b'127.0.0.1')
+            server_hostname='127.0.0.1')
         with ExpectLog(gen_log, "SSL Error"):
             with self.assertRaises(ssl.SSLError):
                 # The client fails to connect with an SSL error.
diff --git a/tornado/test/runtests.py b/tornado/test/runtests.py
index 8c1b6a54..7cabbaf1 100644
--- a/tornado/test/runtests.py
+++ b/tornado/test/runtests.py
@@ -125,13 +125,6 @@ def main():
     # Twisted 15.0.0 triggers some warnings on py3 with -bb.
     warnings.filterwarnings("ignore", category=BytesWarning,
                             module=r"twisted\..*")
-    # The __aiter__ protocol changed in python 3.5.2.
-    # Silence the warning until we can drop 3.5.[01].
-    warnings.filterwarnings("ignore", category=PendingDeprecationWarning,
-                            message=".*legacy __aiter__ protocol")
-    # 3.5.2's PendingDeprecationWarning became a DeprecationWarning in 3.6.
-    warnings.filterwarnings("ignore", category=DeprecationWarning,
-                            message=".*legacy __aiter__ protocol")
 
     logging.getLogger("tornado.access").setLevel(logging.CRITICAL)
 
diff --git a/tornado/test/util.py b/tornado/test/util.py
index 5f534e84..19887c10 100644
--- a/tornado/test/util.py
+++ b/tornado/test/util.py
@@ -43,6 +43,14 @@ skipBefore35 = unittest.skipIf(sys.version_info < (3, 5), 'PEP 492 (async/await)
 skipNotCPython = unittest.skipIf(platform.python_implementation() != 'CPython',
                                  'Not CPython implementation')
 
+# Used for tests affected by
+# https://bitbucket.org/pypy/pypy/issues/2616/incomplete-error-handling-in
+# TODO: remove this after pypy3 5.8 is obsolete.
+skipPypy3V58 = unittest.skipIf(platform.python_implementation() == 'PyPy' and
+                               sys.version_info > (3,) and
+                               sys.pypy_version_info < (5, 9),
+                               'pypy3 5.8 has buggy ssl module')
+
 
 def refusing_port():
     """Returns a local port number that will refuse all connections.