From f81a25e1776fd16511bf8e399500a048def920d5 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Fri, 28 Feb 2014 08:48:26 -0600 Subject: [PATCH 01/21] define and use errno_from_exception abstraction If an OSError or IOError are instantiated without an errno value, e.g. e = OSError(). The existing code would give an IndexError: tuple index out of range. However there have been cases that the errno attribute wasn't populated so instead of switching to it this introduces a helper function to ensure we get the errno value through whatever means possible. --- tornado/ioloop.py | 7 +++---- tornado/iostream.py | 10 +++++----- tornado/netutil.py | 10 +++++----- tornado/process.py | 5 +++-- tornado/tcpserver.py | 3 ++- tornado/util.py | 18 ++++++++++++++++++ 6 files changed, 36 insertions(+), 17 deletions(-) diff --git a/tornado/ioloop.py b/tornado/ioloop.py index 77fb4659..80496e19 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -45,6 +45,7 @@ from tornado.concurrent import TracebackFuture, is_future from tornado.log import app_log, gen_log from tornado import stack_context from tornado.util import Configurable +from tornado.util import errno_from_exception try: import signal @@ -724,9 +725,7 @@ class PollIOLoop(IOLoop): # two ways EINTR might be signaled: # * e.errno == errno.EINTR # * e.args is like (errno.EINTR, 'Interrupted system call') - if (getattr(e, 'errno', None) == errno.EINTR or - (isinstance(getattr(e, 'args', None), tuple) and - len(e.args) == 2 and e.args[0] == errno.EINTR)): + if errno_from_exception(e) == errno.EINTR: continue else: raise @@ -746,7 +745,7 @@ class PollIOLoop(IOLoop): fd_obj, handler_func = self._handlers[fd] handler_func(fd_obj, events) except (OSError, IOError) as e: - if e.args[0] == errno.EPIPE: + if errno_from_exception(e) == errno.EPIPE: # Happens when the client closes the connection pass else: diff --git a/tornado/iostream.py b/tornado/iostream.py index faf657a4..40e75a53 100644 --- a/tornado/iostream.py +++ b/tornado/iostream.py @@ -40,7 +40,7 @@ from tornado import ioloop from tornado.log import gen_log, app_log from tornado.netutil import ssl_wrap_socket, ssl_match_hostname, SSLCertificateError from tornado import stack_context -from tornado.util import bytes_type +from tornado.util import bytes_type, errno_from_exception try: from tornado.platform.posix import _set_nonblocking @@ -766,8 +766,8 @@ class IOStream(BaseIOStream): # returned immediately when attempting to connect to # localhost, so handle them the same way as an error # reported later in _handle_connect. - if (e.args[0] != errno.EINPROGRESS and - e.args[0] not in _ERRNO_WOULDBLOCK): + if (errno_from_exception(e) != errno.EINPROGRESS and + errno_from_exception(e) not in _ERRNO_WOULDBLOCK): gen_log.warning("Connect error on fd %s: %s", self.socket.fileno(), e) self.close(exc_info=True) @@ -1026,9 +1026,9 @@ class PipeIOStream(BaseIOStream): try: chunk = os.read(self.fd, self.read_chunk_size) except (IOError, OSError) as e: - if e.args[0] in _ERRNO_WOULDBLOCK: + if errno_from_exception(e) in _ERRNO_WOULDBLOCK: return None - elif e.args[0] == errno.EBADF: + elif errno_from_exception(e) == errno.EBADF: # If the writing half of a pipe is closed, select will # report it as readable but reads will fail with EBADF. self.close(exc_info=True) diff --git a/tornado/netutil.py b/tornado/netutil.py index a4699138..171873e6 100644 --- a/tornado/netutil.py +++ b/tornado/netutil.py @@ -27,7 +27,7 @@ import stat from tornado.concurrent import dummy_executor, run_on_executor from tornado.ioloop import IOLoop from tornado.platform.auto import set_close_exec -from tornado.util import u, Configurable +from tornado.util import u, Configurable, errno_from_exception if hasattr(ssl, 'match_hostname') and hasattr(ssl, 'CertificateError'): # python 3.2+ ssl_match_hostname = ssl.match_hostname @@ -84,7 +84,7 @@ def bind_sockets(port, address=None, family=socket.AF_UNSPEC, backlog=128, flags try: sock = socket.socket(af, socktype, proto) except socket.error as e: - if e.args[0] == errno.EAFNOSUPPORT: + if errno_from_exception(e) == errno.EAFNOSUPPORT: continue raise set_close_exec(sock.fileno()) @@ -133,7 +133,7 @@ if hasattr(socket, 'AF_UNIX'): try: st = os.stat(file) except OSError as err: - if err.errno != errno.ENOENT: + if errno_from_exception(err) != errno.ENOENT: raise else: if stat.S_ISSOCK(st.st_mode): @@ -165,12 +165,12 @@ def add_accept_handler(sock, callback, io_loop=None): except socket.error as e: # EWOULDBLOCK and EAGAIN indicate we have accepted every # connection that is available. - if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): + if errno_from_exception(e) in (errno.EWOULDBLOCK, errno.EAGAIN): return # ECONNABORTED indicates that there was a connection # but it was closed while still in the accept queue. # (observed on FreeBSD). - if e.args[0] == errno.ECONNABORTED: + if errno_from_exception(e) == errno.ECONNABORTED: continue raise callback(connection, address) diff --git a/tornado/process.py b/tornado/process.py index 942c5c3f..74575d05 100644 --- a/tornado/process.py +++ b/tornado/process.py @@ -35,6 +35,7 @@ from tornado.iostream import PipeIOStream from tornado.log import gen_log from tornado.platform.auto import set_close_exec from tornado import stack_context +from tornado.util import errno_from_exception try: long # py2 @@ -136,7 +137,7 @@ def fork_processes(num_processes, max_restarts=100): try: pid, status = os.wait() except OSError as e: - if e.errno == errno.EINTR: + if errno_from_exception(e) == errno.EINTR: continue raise if pid not in children: @@ -283,7 +284,7 @@ class Subprocess(object): try: ret_pid, status = os.waitpid(pid, os.WNOHANG) except OSError as e: - if e.args[0] == errno.ECHILD: + if errno_from_exception(e) == errno.ECHILD: return if ret_pid == 0: return diff --git a/tornado/tcpserver.py b/tornado/tcpserver.py index c0773732..81d983ff 100644 --- a/tornado/tcpserver.py +++ b/tornado/tcpserver.py @@ -27,6 +27,7 @@ from tornado.ioloop import IOLoop from tornado.iostream import IOStream, SSLIOStream from tornado.netutil import bind_sockets, add_accept_handler, ssl_wrap_socket from tornado import process +from tornado.util import errno_from_exception class TCPServer(object): @@ -231,7 +232,7 @@ class TCPServer(object): # SSLIOStream._do_ssl_handshake). # To test this behavior, try nmap with the -sT flag. # https://github.com/facebook/tornado/pull/750 - if err.args[0] in (errno.ECONNABORTED, errno.EINVAL): + if errno_from_exception(err) in (errno.ECONNABORTED, errno.EINVAL): return connection.close() else: raise diff --git a/tornado/util.py b/tornado/util.py index cc532222..9c341759 100644 --- a/tornado/util.py +++ b/tornado/util.py @@ -132,6 +132,24 @@ def exec_in(code, glob, loc=None): """) +def errno_from_exception(e): + """Provides the errno from an Exception object. + + There are cases that the errno attribute was not set so we pull + the errno out of the args but if someone instatiates an Exception + without any args you will get a tuple error. So this function + abstracts all that behavior to give you a safe way to get the + errno. + """ + + if hasattr(e, 'errno'): + return e.errno + elif e.args: + return e.args[0] + else: + return None + + class Configurable(object): """Base class for configurable interfaces. From 41a8f1a393a56eff00e60ab6b552fdbc38c64486 Mon Sep 17 00:00:00 2001 From: Meng Zhuo Date: Sun, 2 Mar 2014 22:20:49 +0800 Subject: [PATCH 02/21] update Template Loader with 'with' statment --- tornado/template.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tornado/template.py b/tornado/template.py index db5a528d..94127e79 100644 --- a/tornado/template.py +++ b/tornado/template.py @@ -367,10 +367,9 @@ class Loader(BaseLoader): def _create_template(self, name): path = os.path.join(self.root, name) - f = open(path, "rb") - template = Template(f.read(), name=name, loader=self) - f.close() - return template + with open(path, "rb") as f: + template = Template(f.read(), name=name, loader=self) + return template class DictLoader(BaseLoader): From d4627e09bd7d919acec19543ff1ed689fa0eb7c9 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sun, 2 Mar 2014 11:06:06 -0500 Subject: [PATCH 03/21] Exclude another timing-sensitive test from the travis build. --- tornado/test/httpclient_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py index 3b19c20e..7ffddbc3 100644 --- a/tornado/test/httpclient_test.py +++ b/tornado/test/httpclient_test.py @@ -18,7 +18,7 @@ from tornado.log import gen_log from tornado import netutil from tornado.stack_context import ExceptionStackContext, NullContext from tornado.testing import AsyncHTTPTestCase, bind_unused_port, gen_test, ExpectLog -from tornado.test.util import unittest +from tornado.test.util import unittest, skipOnTravis from tornado.util import u, bytes_type from tornado.web import Application, RequestHandler, url @@ -110,6 +110,7 @@ class HTTPClientCommonTestCase(AsyncHTTPTestCase): url("/all_methods", AllMethodsHandler), ], gzip=True) + @skipOnTravis def test_hello_world(self): response = self.fetch("/hello") self.assertEqual(response.code, 200) From a749b999adfd5ab51cecdf1f469818d4983d1b60 Mon Sep 17 00:00:00 2001 From: yac Date: Sat, 8 Mar 2014 01:20:09 +0100 Subject: [PATCH 04/21] twisted can be installed without twisted.names fixes #1007 --- tornado/test/netutil_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tornado/test/netutil_test.py b/tornado/test/netutil_test.py index 89d782e6..663df156 100644 --- a/tornado/test/netutil_test.py +++ b/tornado/test/netutil_test.py @@ -26,6 +26,7 @@ else: try: import twisted + import twisted.names except ImportError: twisted = None else: From 1759dc694adaaa49a7b010c0a05e2cdc69e93623 Mon Sep 17 00:00:00 2001 From: xuxiang Date: Mon, 10 Mar 2014 18:22:39 +0800 Subject: [PATCH 05/21] remove useless code of BaseIOStream --- tornado/iostream.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tornado/iostream.py b/tornado/iostream.py index 40e75a53..651a3a89 100644 --- a/tornado/iostream.py +++ b/tornado/iostream.py @@ -203,7 +203,6 @@ class BaseIOStream(object): self._run_read_callback(self._consume(self._read_buffer_size)) return future self._read_until_close = True - self._streaming_callback = stack_context.wrap(streaming_callback) self._try_inline_read() return future From 2aef15dfa3e0ada2d17dae90be6ef645607d20ce Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Thu, 13 Mar 2014 10:04:36 -0400 Subject: [PATCH 06/21] Fix command-line tornado.httpclient on python 3. --- tornado/httpclient.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tornado/httpclient.py b/tornado/httpclient.py index 9b42d401..bb11e113 100644 --- a/tornado/httpclient.py +++ b/tornado/httpclient.py @@ -34,7 +34,7 @@ import time import weakref from tornado.concurrent import TracebackFuture -from tornado.escape import utf8 +from tornado.escape import utf8, native_str from tornado import httputil, stack_context from tornado.ioloop import IOLoop from tornado.util import Configurable @@ -556,7 +556,7 @@ def main(): if options.print_headers: print(response.headers) if options.print_body: - print(response.body) + print(native_str(response.body)) client.close() if __name__ == "__main__": From d2226ff81b566d6af3df7da6a6461cf6a8356bc3 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Thu, 13 Mar 2014 10:15:41 -0400 Subject: [PATCH 07/21] Improve simple_httpclient ssl configuration to pass howsmyssl.com. Remove insecure cipher suites and disable TLS compression. The option to disable compression was only added in Python 3.3 so we do not pass the test on older versions, but we come as close as possible with the APIs available. Closes #1014. --- tornado/netutil.py | 4 ++++ tornado/simple_httpclient.py | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tornado/netutil.py b/tornado/netutil.py index 171873e6..d12a2160 100644 --- a/tornado/netutil.py +++ b/tornado/netutil.py @@ -390,6 +390,10 @@ def ssl_options_to_context(ssl_options): context.load_verify_locations(ssl_options['ca_certs']) if 'ciphers' in ssl_options: context.set_ciphers(ssl_options['ciphers']) + if hasattr(ssl, 'OP_NO_COMPRESSION'): + # Disable TLS compression to avoid CRIME and related attacks. + # This constant wasn't added until python 3.3. + context.options |= ssl.OP_NO_COMPRESSION return context diff --git a/tornado/simple_httpclient.py b/tornado/simple_httpclient.py index 73bfee89..c7e6f1a9 100644 --- a/tornado/simple_httpclient.py +++ b/tornado/simple_httpclient.py @@ -236,7 +236,9 @@ class _HTTPConnection(object): # but nearly all servers support both SSLv3 and TLSv1: # http://blog.ivanristic.com/2011/09/ssl-survey-protocol-support.html if sys.version_info >= (2, 7): - ssl_options["ciphers"] = "DEFAULT:!SSLv2" + # In addition to disabling SSLv2, we also exclude certain + # classes of insecure ciphers. + ssl_options["ciphers"] = "DEFAULT:!SSLv2:!EXPORT:!DES" else: # This is really only necessary for pre-1.0 versions # of openssl, but python 2.6 doesn't expose version From 99474fe89b4cea763e3fd449113a28a8d92d6729 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sat, 15 Mar 2014 00:45:09 -0400 Subject: [PATCH 08/21] Refactor unittest/unittest2 imports to fix issues with unittest2 on py3. Never use unittest2 on python 3 (it appears to not interoperate with the standard unittest module). On python 2, use the same logic in tornado.testing and tornado.test.util to select an implementation. Fixes #1005. --- tornado/test/util.py | 9 ++++++--- tornado/testing.py | 12 +++++++++--- tox.ini | 13 +++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/tornado/test/util.py b/tornado/test/util.py index 36043104..6a0fe807 100644 --- a/tornado/test/util.py +++ b/tornado/test/util.py @@ -5,10 +5,13 @@ import sys # Encapsulate the choice of unittest or unittest2 here. # To be used as 'from tornado.test.util import unittest'. -if sys.version_info >= (2, 7): - import unittest -else: +if sys.version_info < (2, 7): + # In py26, we must always use unittest2. import unittest2 as unittest +else: + # Otherwise, use whichever version of unittest was imported in + # tornado.testing. + from tornado.testing import unittest skipIfNonUnix = unittest.skipIf(os.name != 'posix' or sys.platform == 'cygwin', "non-unix platform") diff --git a/tornado/testing.py b/tornado/testing.py index 96fdd32b..647f349c 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -49,10 +49,16 @@ except ImportError: # (either py27+ or unittest2) so tornado.test.util enforces # this requirement, but for other users of tornado.testing we want # to allow the older version if unitest2 is not available. -try: - import unittest2 as unittest -except ImportError: +if sys.version_info >= (3,): + # On python 3, mixing unittest2 and unittest (including doctest) + # doesn't seem to work, so always use unittest. import unittest +else: + # On python 2, prefer unittest2 when available. + try: + import unittest2 as unittest + except ImportError: + import unittest _next_port = 10000 diff --git a/tox.ini b/tox.ini index 9f7cf379..3d67726c 100644 --- a/tox.ini +++ b/tox.ini @@ -36,6 +36,7 @@ envlist = py2-opt, py3-opt, py3-utf8, py2-locale, + py27-unittest2, py33-unittest2, # Ensure the sphinx build has no errors or warnings py2-docs @@ -78,6 +79,12 @@ deps = pycurl twisted>=11.0.0 +# unittest2 doesn't add anything we need on 2.7, but we should ensure that +# its existence doesn't break anything due to conditional imports. +[testenv:py27-unittest2] +basepython = python2.7 +deps = unittest2 + # 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 dependencies on this setting. @@ -104,6 +111,12 @@ setenv = TORNADO_EXTENSION=1 deps = pycurl>=7.19.3 +# See comment on py27-unittest2. +[testenv:py33-unittest2] +basepython = python3.3 +setenv = TORNADO_EXTENSION=1 +deps = unittest2py3k + [testenv:py34-full] basepython = python3.4 setenv = TORNADO_EXTENSION=1 From 4ff47bb4869212818ef2822256f70ef8b46e0b55 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sat, 15 Mar 2014 00:50:25 -0400 Subject: [PATCH 09/21] Update links for comments about json list output. The vulnerability in the older link affects only ancient browsers, but there are other issues that are more recent. Fixes #1009. --- tornado/web.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tornado/web.py b/tornado/web.py index 2d990805..e2ef7eef 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -598,7 +598,8 @@ class RequestHandler(object): Note that lists are not converted to JSON because of a potential cross-site security vulnerability. All JSON output should be wrapped in a dictionary. More details at - http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx + http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ and + https://github.com/facebook/tornado/issues/1009 """ if self._finished: raise RuntimeError("Cannot write() after finish(). May be caused " From dd45026300e6c43c309398ab983abc945f0a994b Mon Sep 17 00:00:00 2001 From: MinRK Date: Fri, 21 Mar 2014 10:59:25 -0700 Subject: [PATCH 10/21] add IOLoop.clear_instance like clear_current, but for the global IOLoop instance --- tornado/ioloop.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tornado/ioloop.py b/tornado/ioloop.py index 80496e19..421bf876 100644 --- a/tornado/ioloop.py +++ b/tornado/ioloop.py @@ -157,6 +157,12 @@ class IOLoop(Configurable): assert not IOLoop.initialized() IOLoop._instance = self + @staticmethod + def clear_instance(): + """Clear the global `IOLoop` instance.""" + if hasattr(IOLoop, "_instance"): + del IOLoop._instance + @staticmethod def current(): """Returns the current thread's `IOLoop`. From 958c3f322c102e0019724008bf786d6c4982b8b1 Mon Sep 17 00:00:00 2001 From: huangxiaokai Date: Sun, 23 Mar 2014 18:25:25 +0800 Subject: [PATCH 11/21] Fix a mistake in template document. --- tornado/template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tornado/template.py b/tornado/template.py index 94127e79..8bb0ac0a 100644 --- a/tornado/template.py +++ b/tornado/template.py @@ -180,7 +180,7 @@ with ``{# ... #}``. ``{% set *x* = *y* %}`` Sets a local variable. -``{% try %}...{% except %}...{% finally %}...{% else %}...{% end %}`` +``{% try %}...{% except %}...{% else %}...{% finally %}...{% end %}`` Same as the python ``try`` statement. ``{% while *condition* %}... {% end %}`` From 142b5fb92f52cdb20d367a3809cddc4d32cd1a33 Mon Sep 17 00:00:00 2001 From: shinriyo Date: Wed, 26 Mar 2014 10:04:07 +0900 Subject: [PATCH 12/21] tab to space tab to space and indent fix --- demos/websocket/static/chat.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/demos/websocket/static/chat.js b/demos/websocket/static/chat.js index 8ff1b4bd..b4bb18a9 100644 --- a/demos/websocket/static/chat.js +++ b/demos/websocket/static/chat.js @@ -51,10 +51,10 @@ var updater = { start: function() { var url = "ws://" + location.host + "/chatsocket"; - updater.socket = new WebSocket(url); - updater.socket.onmessage = function(event) { - updater.showMessage(JSON.parse(event.data)); - } + updater.socket = new WebSocket(url); + updater.socket.onmessage = function(event) { + updater.showMessage(JSON.parse(event.data)); + } }, showMessage: function(message) { From ccbe5a2787c2721f2aa0b7f5f08f89183aef2415 Mon Sep 17 00:00:00 2001 From: Akihiro Yamazaki Date: Thu, 27 Mar 2014 09:37:29 +0900 Subject: [PATCH 13/21] put assertion outside of the exception handler --- tornado/test/concurrent_test.py | 2 +- tornado/test/httpclient_test.py | 5 +++-- tornado/test/ioloop_test.py | 3 ++- tornado/test/template_test.py | 22 ++++++++++++---------- tornado/test/testing_test.py | 11 ++++++----- tornado/test/util_test.py | 3 ++- 6 files changed, 26 insertions(+), 20 deletions(-) diff --git a/tornado/test/concurrent_test.py b/tornado/test/concurrent_test.py index 5e93ad6a..2506ef45 100644 --- a/tornado/test/concurrent_test.py +++ b/tornado/test/concurrent_test.py @@ -168,7 +168,7 @@ class ReturnFutureTest(AsyncTestCase): self.fail("didn't get expected exception") except ZeroDivisionError: tb = traceback.extract_tb(sys.exc_info()[2]) - self.assertIn(self.expected_frame, tb) + self.assertIn(self.expected_frame, tb) # The following series of classes demonstrate and test various styles # of use, with and without generators and futures. diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py index 7ffddbc3..984f5e0c 100644 --- a/tornado/test/httpclient_test.py +++ b/tornado/test/httpclient_test.py @@ -359,8 +359,9 @@ Transfer-Encoding: chunked try: yield self.http_client.fetch(self.get_url('/notfound')) except HTTPError as e: - self.assertEqual(e.code, 404) - self.assertEqual(e.response.code, 404) + got_exception = e + self.assertEqual(got_exception.code, 404) + self.assertEqual(got_exception.response.code, 404) @gen_test def test_reuse_request_from_response(self): diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index ff26bde1..bd069c24 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -116,8 +116,9 @@ class TestIOLoop(AsyncTestCase): try: other_ioloop.add_callback(lambda: None) except RuntimeError as e: - self.assertEqual("IOLoop is closing", str(e)) + got_exception = e break + self.assertEqual("IOLoop is closing", str(got_exception)) def test_handle_callback_exception(self): # IOLoop.handle_callback_exception can be overridden to catch diff --git a/tornado/test/template_test.py b/tornado/test/template_test.py index f3a9e059..c1b1e48e 100644 --- a/tornado/test/template_test.py +++ b/tornado/test/template_test.py @@ -183,7 +183,8 @@ three try: loader.load("test.html").generate() except ZeroDivisionError: - self.assertTrue("# test.html:2" in traceback.format_exc()) + exc_stack = traceback.format_exc() + self.assertTrue("# test.html:2" in exc_stack) def test_error_line_number_directive(self): loader = DictLoader({"test.html": """one @@ -193,7 +194,8 @@ three{%end%} try: loader.load("test.html").generate() except ZeroDivisionError: - self.assertTrue("# test.html:2" in traceback.format_exc()) + exc_stack = traceback.format_exc() + self.assertTrue("# test.html:2" in exc_stack) def test_error_line_number_module(self): loader = DictLoader({ @@ -204,8 +206,8 @@ three{%end%} loader.load("base.html").generate() except ZeroDivisionError: exc_stack = traceback.format_exc() - self.assertTrue('# base.html:1' in exc_stack) - self.assertTrue('# sub.html:1' in exc_stack) + self.assertTrue('# base.html:1' in exc_stack) + self.assertTrue('# sub.html:1' in exc_stack) def test_error_line_number_include(self): loader = DictLoader({ @@ -215,8 +217,8 @@ three{%end%} try: loader.load("base.html").generate() except ZeroDivisionError: - self.assertTrue("# sub.html:1 (via base.html:1)" in - traceback.format_exc()) + exc_stack = traceback.format_exc() + self.assertTrue("# sub.html:1 (via base.html:1)" in exc_stack) def test_error_line_number_extends_base_error(self): loader = DictLoader({ @@ -241,8 +243,8 @@ three{%end%} try: loader.load("sub.html").generate() except ZeroDivisionError: - self.assertTrue("# sub.html:4 (via base.html:1)" in - traceback.format_exc()) + exc_stack = traceback.format_exc() + self.assertTrue("# sub.html:4 (via base.html:1)" in exc_stack) def test_multi_includes(self): loader = DictLoader({ @@ -253,8 +255,8 @@ three{%end%} try: loader.load("a.html").generate() except ZeroDivisionError: - self.assertTrue("# c.html:1 (via b.html:1, a.html:1)" in - traceback.format_exc()) + exc_stack = traceback.format_exc() + self.assertTrue("# c.html:1 (via b.html:1, a.html:1)" in exc_stack) class AutoEscapeTest(unittest.TestCase): diff --git a/tornado/test/testing_test.py b/tornado/test/testing_test.py index aabdaced..ef545e76 100644 --- a/tornado/test/testing_test.py +++ b/tornado/test/testing_test.py @@ -155,11 +155,12 @@ class GenTest(AsyncTestCase): test(self) self.fail("did not get expected exception") except ioloop.TimeoutError: - # The stack trace should blame the add_timeout line, not just - # unrelated IOLoop/testing internals. - self.assertIn( - "gen.Task(self.io_loop.add_timeout, self.io_loop.time() + 1)", - traceback.format_exc()) + exc_stack = traceback.format_exc() + # The stack trace should blame the add_timeout line, not just + # unrelated IOLoop/testing internals. + self.assertIn( + "gen.Task(self.io_loop.add_timeout, self.io_loop.time() + 1)", + exc_stack) self.finished = True diff --git a/tornado/test/util_test.py b/tornado/test/util_test.py index 41ccbb9a..fa965fbe 100644 --- a/tornado/test/util_test.py +++ b/tornado/test/util_test.py @@ -30,7 +30,8 @@ class RaiseExcInfoTest(unittest.TestCase): raise_exc_info(exc_info) self.fail("didn't get expected exception") except TwoArgException as e: - self.assertIs(e, exc_info[1]) + got_exception = e + self.assertIs(got_exception, exc_info[1]) class TestConfigurable(Configurable): From e914a5143e8fd99c4904eedc7547d0d637d752de Mon Sep 17 00:00:00 2001 From: Akihiro Yamazaki Date: Fri, 28 Mar 2014 00:27:39 +0900 Subject: [PATCH 14/21] use "with self.assertRaises" pattern --- tornado/test/concurrent_test.py | 6 ++--- tornado/test/httpclient_test.py | 8 +++--- tornado/test/ioloop_test.py | 8 ++---- tornado/test/template_test.py | 45 ++++++++++++--------------------- tornado/test/testing_test.py | 11 +++----- tornado/test/util_test.py | 7 ++--- 6 files changed, 28 insertions(+), 57 deletions(-) diff --git a/tornado/test/concurrent_test.py b/tornado/test/concurrent_test.py index 2506ef45..edd70c9a 100644 --- a/tornado/test/concurrent_test.py +++ b/tornado/test/concurrent_test.py @@ -163,11 +163,9 @@ class ReturnFutureTest(AsyncTestCase): self.expected_frame = traceback.extract_tb( sys.exc_info()[2], limit=1)[0] raise - try: + with self.assertRaises(ZeroDivisionError): yield f() - self.fail("didn't get expected exception") - except ZeroDivisionError: - tb = traceback.extract_tb(sys.exc_info()[2]) + tb = traceback.extract_tb(sys.exc_info()[2]) self.assertIn(self.expected_frame, tb) # The following series of classes demonstrate and test various styles diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py index 984f5e0c..78daa74d 100644 --- a/tornado/test/httpclient_test.py +++ b/tornado/test/httpclient_test.py @@ -356,12 +356,10 @@ Transfer-Encoding: chunked @gen_test def test_future_http_error(self): - try: + with self.assertRaises(HTTPError) as context: yield self.http_client.fetch(self.get_url('/notfound')) - except HTTPError as e: - got_exception = e - self.assertEqual(got_exception.code, 404) - self.assertEqual(got_exception.response.code, 404) + self.assertEqual(context.exception.code, 404) + self.assertEqual(context.exception.response.code, 404) @gen_test def test_reuse_request_from_response(self): diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index bd069c24..b7d1f3a6 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -112,13 +112,9 @@ class TestIOLoop(AsyncTestCase): thread = threading.Thread(target=target) thread.start() closing.wait() - for i in range(1000): - try: + with self.assertRaisesRegexp(RuntimeError, "\AIOLoop is closing\Z"): + for i in range(1000): other_ioloop.add_callback(lambda: None) - except RuntimeError as e: - got_exception = e - break - self.assertEqual("IOLoop is closing", str(got_exception)) def test_handle_callback_exception(self): # IOLoop.handle_callback_exception can be overridden to catch diff --git a/tornado/test/template_test.py b/tornado/test/template_test.py index c1b1e48e..181008dc 100644 --- a/tornado/test/template_test.py +++ b/tornado/test/template_test.py @@ -149,20 +149,14 @@ try{% set y = 1/x %} self.assertEqual(result, b"013456") def test_break_outside_loop(self): - try: + with self.assertRaises(ParseError): Template(utf8("{% break %}")) - raise Exception("Did not get expected exception") - except ParseError: - pass def test_break_in_apply(self): # This test verifies current behavior, although of course it would # be nice if apply didn't cause seemingly unrelated breakage - try: + with self.assertRaises(ParseError): Template(utf8("{% for i in [] %}{% apply foo %}{% break %}{% end %}{% end %}")) - raise Exception("Did not get expected exception") - except ParseError: - pass @unittest.skipIf(sys.version_info >= division.getMandatoryRelease(), 'no testable future imports') @@ -180,10 +174,9 @@ class StackTraceTest(unittest.TestCase): two{{1/0}} three """}) - try: + with self.assertRaises(ZeroDivisionError): loader.load("test.html").generate() - except ZeroDivisionError: - exc_stack = traceback.format_exc() + exc_stack = traceback.format_exc() self.assertTrue("# test.html:2" in exc_stack) def test_error_line_number_directive(self): @@ -191,10 +184,9 @@ three two{%if 1/0%} three{%end%} """}) - try: + with self.assertRaises(ZeroDivisionError): loader.load("test.html").generate() - except ZeroDivisionError: - exc_stack = traceback.format_exc() + exc_stack = traceback.format_exc() self.assertTrue("# test.html:2" in exc_stack) def test_error_line_number_module(self): @@ -202,10 +194,9 @@ three{%end%} "base.html": "{% module Template('sub.html') %}", "sub.html": "{{1/0}}", }, namespace={"_tt_modules": ObjectDict({"Template": lambda path, **kwargs: loader.load(path).generate(**kwargs)})}) - try: + with self.assertRaises(ZeroDivisionError): loader.load("base.html").generate() - except ZeroDivisionError: - exc_stack = traceback.format_exc() + exc_stack = traceback.format_exc() self.assertTrue('# base.html:1' in exc_stack) self.assertTrue('# sub.html:1' in exc_stack) @@ -214,10 +205,9 @@ three{%end%} "base.html": "{% include 'sub.html' %}", "sub.html": "{{1/0}}", }) - try: + with self.assertRaises(ZeroDivisionError): loader.load("base.html").generate() - except ZeroDivisionError: - exc_stack = traceback.format_exc() + exc_stack = traceback.format_exc() self.assertTrue("# sub.html:1 (via base.html:1)" in exc_stack) def test_error_line_number_extends_base_error(self): @@ -225,10 +215,9 @@ three{%end%} "base.html": "{{1/0}}", "sub.html": "{% extends 'base.html' %}", }) - try: + with self.assertRaises(ZeroDivisionError): loader.load("sub.html").generate() - except ZeroDivisionError: - exc_stack = traceback.format_exc() + exc_stack = traceback.format_exc() self.assertTrue("# base.html:1" in exc_stack) def test_error_line_number_extends_sub_error(self): @@ -240,10 +229,9 @@ three{%end%} {{1/0}} {% end %} """}) - try: + with self.assertRaises(ZeroDivisionError): loader.load("sub.html").generate() - except ZeroDivisionError: - exc_stack = traceback.format_exc() + exc_stack = traceback.format_exc() self.assertTrue("# sub.html:4 (via base.html:1)" in exc_stack) def test_multi_includes(self): @@ -252,10 +240,9 @@ three{%end%} "b.html": "{% include 'c.html' %}", "c.html": "{{1/0}}", }) - try: + with self.assertRaises(ZeroDivisionError): loader.load("a.html").generate() - except ZeroDivisionError: - exc_stack = traceback.format_exc() + exc_stack = traceback.format_exc() self.assertTrue("# c.html:1 (via b.html:1, a.html:1)" in exc_stack) diff --git a/tornado/test/testing_test.py b/tornado/test/testing_test.py index ef545e76..92eb9a48 100644 --- a/tornado/test/testing_test.py +++ b/tornado/test/testing_test.py @@ -28,11 +28,8 @@ def set_environ(name, value): class AsyncTestCaseTest(AsyncTestCase): def test_exception_in_callback(self): self.io_loop.add_callback(lambda: 1 / 0) - try: + with self.assertRaises(ZeroDivisionError): self.wait() - self.fail("did not get expected exception") - except ZeroDivisionError: - pass def test_wait_timeout(self): time = self.io_loop.time @@ -151,11 +148,9 @@ class GenTest(AsyncTestCase): # This can't use assertRaises because we need to inspect the # exc_info triple (and not just the exception object) - try: + with self.assertRaises(ioloop.TimeoutError): test(self) - self.fail("did not get expected exception") - except ioloop.TimeoutError: - exc_stack = traceback.format_exc() + exc_stack = traceback.format_exc() # The stack trace should blame the add_timeout line, not just # unrelated IOLoop/testing internals. self.assertIn( diff --git a/tornado/test/util_test.py b/tornado/test/util_test.py index fa965fbe..fab94e7d 100644 --- a/tornado/test/util_test.py +++ b/tornado/test/util_test.py @@ -26,12 +26,9 @@ class RaiseExcInfoTest(unittest.TestCase): raise TwoArgException(1, 2) except TwoArgException: exc_info = sys.exc_info() - try: + with self.assertRaises(TwoArgException) as context: raise_exc_info(exc_info) - self.fail("didn't get expected exception") - except TwoArgException as e: - got_exception = e - self.assertIs(got_exception, exc_info[1]) + self.assertIs(context.exception, exc_info[1]) class TestConfigurable(Configurable): From 32c7a328ed2c00d0ec9adc62b036e1bee65ad146 Mon Sep 17 00:00:00 2001 From: Akihiro Yamazaki Date: Sun, 30 Mar 2014 23:15:46 +0900 Subject: [PATCH 15/21] correct testcases --- tornado/test/concurrent_test.py | 8 +++-- tornado/test/ioloop_test.py | 7 ++-- tornado/test/template_test.py | 64 +++++++++++++++++++++------------ tornado/test/testing_test.py | 20 ++++++----- tornado/test/util_test.py | 6 ++-- 5 files changed, 67 insertions(+), 38 deletions(-) diff --git a/tornado/test/concurrent_test.py b/tornado/test/concurrent_test.py index edd70c9a..5e93ad6a 100644 --- a/tornado/test/concurrent_test.py +++ b/tornado/test/concurrent_test.py @@ -163,10 +163,12 @@ class ReturnFutureTest(AsyncTestCase): self.expected_frame = traceback.extract_tb( sys.exc_info()[2], limit=1)[0] raise - with self.assertRaises(ZeroDivisionError): + try: yield f() - tb = traceback.extract_tb(sys.exc_info()[2]) - self.assertIn(self.expected_frame, tb) + self.fail("didn't get expected exception") + except ZeroDivisionError: + tb = traceback.extract_tb(sys.exc_info()[2]) + self.assertIn(self.expected_frame, tb) # The following series of classes demonstrate and test various styles # of use, with and without generators and futures. diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index b7d1f3a6..ff26bde1 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -112,9 +112,12 @@ class TestIOLoop(AsyncTestCase): thread = threading.Thread(target=target) thread.start() closing.wait() - with self.assertRaisesRegexp(RuntimeError, "\AIOLoop is closing\Z"): - for i in range(1000): + for i in range(1000): + try: other_ioloop.add_callback(lambda: None) + except RuntimeError as e: + self.assertEqual("IOLoop is closing", str(e)) + break def test_handle_callback_exception(self): # IOLoop.handle_callback_exception can be overridden to catch diff --git a/tornado/test/template_test.py b/tornado/test/template_test.py index 181008dc..32bbe421 100644 --- a/tornado/test/template_test.py +++ b/tornado/test/template_test.py @@ -149,14 +149,20 @@ try{% set y = 1/x %} self.assertEqual(result, b"013456") def test_break_outside_loop(self): - with self.assertRaises(ParseError): + try: Template(utf8("{% break %}")) + raise Exception("Did not get expected exception") + except ParseError: + pass def test_break_in_apply(self): # This test verifies current behavior, although of course it would # be nice if apply didn't cause seemingly unrelated breakage - with self.assertRaises(ParseError): + try: Template(utf8("{% for i in [] %}{% apply foo %}{% break %}{% end %}{% end %}")) + raise Exception("Did not get expected exception") + except ParseError: + pass @unittest.skipIf(sys.version_info >= division.getMandatoryRelease(), 'no testable future imports') @@ -174,50 +180,58 @@ class StackTraceTest(unittest.TestCase): two{{1/0}} three """}) - with self.assertRaises(ZeroDivisionError): + try: loader.load("test.html").generate() - exc_stack = traceback.format_exc() - self.assertTrue("# test.html:2" in exc_stack) + self.fail("did not get expected exception") + except ZeroDivisionError: + self.assertTrue("# test.html:2" in traceback.format_exc()) def test_error_line_number_directive(self): loader = DictLoader({"test.html": """one two{%if 1/0%} three{%end%} """}) - with self.assertRaises(ZeroDivisionError): + try: loader.load("test.html").generate() - exc_stack = traceback.format_exc() - self.assertTrue("# test.html:2" in exc_stack) + self.fail("did not get expected exception") + except ZeroDivisionError: + self.assertTrue("# test.html:2" in traceback.format_exc()) def test_error_line_number_module(self): loader = DictLoader({ "base.html": "{% module Template('sub.html') %}", "sub.html": "{{1/0}}", }, namespace={"_tt_modules": ObjectDict({"Template": lambda path, **kwargs: loader.load(path).generate(**kwargs)})}) - with self.assertRaises(ZeroDivisionError): + try: loader.load("base.html").generate() - exc_stack = traceback.format_exc() - self.assertTrue('# base.html:1' in exc_stack) - self.assertTrue('# sub.html:1' in exc_stack) + self.fail("did not get expected exception") + except ZeroDivisionError: + exc_stack = traceback.format_exc() + self.assertTrue('# base.html:1' in exc_stack) + self.assertTrue('# sub.html:1' in exc_stack) def test_error_line_number_include(self): loader = DictLoader({ "base.html": "{% include 'sub.html' %}", "sub.html": "{{1/0}}", }) - with self.assertRaises(ZeroDivisionError): + try: loader.load("base.html").generate() - exc_stack = traceback.format_exc() - self.assertTrue("# sub.html:1 (via base.html:1)" in exc_stack) + self.fail("did not get expected exception") + except ZeroDivisionError: + self.assertTrue("# sub.html:1 (via base.html:1)" in + traceback.format_exc()) def test_error_line_number_extends_base_error(self): loader = DictLoader({ "base.html": "{{1/0}}", "sub.html": "{% extends 'base.html' %}", }) - with self.assertRaises(ZeroDivisionError): + try: loader.load("sub.html").generate() - exc_stack = traceback.format_exc() + self.fail("did not get expected exception") + except ZeroDivisionError: + exc_stack = traceback.format_exc() self.assertTrue("# base.html:1" in exc_stack) def test_error_line_number_extends_sub_error(self): @@ -229,10 +243,12 @@ three{%end%} {{1/0}} {% end %} """}) - with self.assertRaises(ZeroDivisionError): + try: loader.load("sub.html").generate() - exc_stack = traceback.format_exc() - self.assertTrue("# sub.html:4 (via base.html:1)" in exc_stack) + self.fail("did not get expected exception") + except ZeroDivisionError: + self.assertTrue("# sub.html:4 (via base.html:1)" in + traceback.format_exc()) def test_multi_includes(self): loader = DictLoader({ @@ -240,10 +256,12 @@ three{%end%} "b.html": "{% include 'c.html' %}", "c.html": "{{1/0}}", }) - with self.assertRaises(ZeroDivisionError): + try: loader.load("a.html").generate() - exc_stack = traceback.format_exc() - self.assertTrue("# c.html:1 (via b.html:1, a.html:1)" in exc_stack) + self.fail("did not get expected exception") + except ZeroDivisionError: + self.assertTrue("# c.html:1 (via b.html:1, a.html:1)" in + traceback.format_exc()) class AutoEscapeTest(unittest.TestCase): diff --git a/tornado/test/testing_test.py b/tornado/test/testing_test.py index 92eb9a48..aabdaced 100644 --- a/tornado/test/testing_test.py +++ b/tornado/test/testing_test.py @@ -28,8 +28,11 @@ def set_environ(name, value): class AsyncTestCaseTest(AsyncTestCase): def test_exception_in_callback(self): self.io_loop.add_callback(lambda: 1 / 0) - with self.assertRaises(ZeroDivisionError): + try: self.wait() + self.fail("did not get expected exception") + except ZeroDivisionError: + pass def test_wait_timeout(self): time = self.io_loop.time @@ -148,14 +151,15 @@ class GenTest(AsyncTestCase): # This can't use assertRaises because we need to inspect the # exc_info triple (and not just the exception object) - with self.assertRaises(ioloop.TimeoutError): + try: test(self) - exc_stack = traceback.format_exc() - # The stack trace should blame the add_timeout line, not just - # unrelated IOLoop/testing internals. - self.assertIn( - "gen.Task(self.io_loop.add_timeout, self.io_loop.time() + 1)", - exc_stack) + self.fail("did not get expected exception") + except ioloop.TimeoutError: + # The stack trace should blame the add_timeout line, not just + # unrelated IOLoop/testing internals. + self.assertIn( + "gen.Task(self.io_loop.add_timeout, self.io_loop.time() + 1)", + traceback.format_exc()) self.finished = True diff --git a/tornado/test/util_test.py b/tornado/test/util_test.py index fab94e7d..41ccbb9a 100644 --- a/tornado/test/util_test.py +++ b/tornado/test/util_test.py @@ -26,9 +26,11 @@ class RaiseExcInfoTest(unittest.TestCase): raise TwoArgException(1, 2) except TwoArgException: exc_info = sys.exc_info() - with self.assertRaises(TwoArgException) as context: + try: raise_exc_info(exc_info) - self.assertIs(context.exception, exc_info[1]) + self.fail("didn't get expected exception") + except TwoArgException as e: + self.assertIs(e, exc_info[1]) class TestConfigurable(Configurable): From 3c66b6a58860113c7f79dacc9ecf8ba08ab0ddc6 Mon Sep 17 00:00:00 2001 From: bdarnell Date: Tue, 8 Apr 2014 08:04:37 -0400 Subject: [PATCH 16/21] Fix typo Closes #1028 --- tornado/httpclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tornado/httpclient.py b/tornado/httpclient.py index bb11e113..e88bcf6c 100644 --- a/tornado/httpclient.py +++ b/tornado/httpclient.py @@ -166,7 +166,7 @@ class AsyncHTTPClient(Configurable): kwargs: ``HTTPRequest(request, **kwargs)`` This method returns a `.Future` whose result is an - `HTTPResponse`. The ``Future`` wil raise an `HTTPError` if + `HTTPResponse`. The ``Future`` will raise an `HTTPError` if the request returned a non-200 response code. If a ``callback`` is given, it will be invoked with the `HTTPResponse`. From f9a44f0c0eaee3a51cd04145786e5a32d0602e2f Mon Sep 17 00:00:00 2001 From: daftshady Date: Wed, 9 Apr 2014 02:50:36 +0900 Subject: [PATCH 17/21] fixed broken reference link --- tornado/platform/common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tornado/platform/common.py b/tornado/platform/common.py index d9c4cf9f..b409a903 100644 --- a/tornado/platform/common.py +++ b/tornado/platform/common.py @@ -15,7 +15,8 @@ class Waker(interface.Waker): and Jython. """ def __init__(self): - # Based on Zope async.py: http://svn.zope.org/zc.ngi/trunk/src/zc/ngi/async.py + # Based on Zope select_trigger.py: + # https://github.com/zopefoundation/Zope/blob/master/src/ZServer/medusa/thread/select_trigger.py self.writer = socket.socket() # Disable buffering -- pulling the trigger sends 1 byte, From 5347ae21bd8d8548a2789b8beb635f906ae7493d Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 8 Apr 2014 11:23:21 -0700 Subject: [PATCH 18/21] Add SystemError to errors from compilation OSX generates a SystemError from a missing compiler; trap with other compilation errors. --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 44e2ed36..0630987b 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,8 @@ if sys.platform == 'win32' and sys.version_info > (2, 6): build_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, IOError) else: - build_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError) + build_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, + SystemError) class custom_build_ext(build_ext): """Allow C extension building to fail. From 73c8c218c5aade5909d03b67a7ad0c0135d2ebf6 Mon Sep 17 00:00:00 2001 From: Kouki Saito Date: Wed, 9 Apr 2014 19:31:27 +0900 Subject: [PATCH 19/21] Delete description for cookie capacity --- demos/twitter/twitterdemo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/demos/twitter/twitterdemo.py b/demos/twitter/twitterdemo.py index 3cc23f9c..bf14c796 100644 --- a/demos/twitter/twitterdemo.py +++ b/demos/twitter/twitterdemo.py @@ -62,6 +62,7 @@ class LoginHandler(BaseHandler, TwitterMixin): def get(self): if self.get_argument('oauth_token', None): user = yield self.get_authenticated_user() + del user["description"] self.set_secure_cookie(self.COOKIE_NAME, json_encode(user)) self.redirect(self.get_argument('next', '/')) else: From 217de13966a758b1410ead4706f343aa7bb0b413 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sat, 26 Apr 2014 19:23:42 -0400 Subject: [PATCH 20/21] Catch up on next-release notes. --- docs/releases/next.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/releases/next.rst b/docs/releases/next.rst index cc563007..bc3337c0 100644 --- a/docs/releases/next.rst +++ b/docs/releases/next.rst @@ -24,6 +24,12 @@ Backwards-compatibility notes of the old ``TracebackFuture`` class. ``TracebackFuture`` is now simply an alias for ``Future``. +`tornado.httpclient` +~~~~~~~~~~~~~~~~~~~~ + +* The command-line HTTP client (``python -m tornado.httpclient $URL``) + now works on Python 3. + `tornado.gen` ~~~~~~~~~~~~~ @@ -40,6 +46,8 @@ Backwards-compatibility notes * `.IOLoop.add_handler` and related methods now accept file-like objects in addition to raw file descriptors. Passing the objects is recommended (when possible) to avoid a garbage-collection-related problem in unit tests. +* New method `.IOLoop.clear_instance` makes it possible to uninstall the + singleton instance. `tornado.iostream` ~~~~~~~~~~~~~~~~~~ @@ -47,6 +55,16 @@ Backwards-compatibility notes * The ``callback`` argument to most `.IOStream` methods is now optional. When called without a callback the method will return a `.Future` for use with coroutines. +* No longer gets confused when an ``IOError`` or ``OSError`` without + an ``errno`` attribute is raised. + +`tornado.netutil` +~~~~~~~~~~~~~~~~~ + +* When `.bind_sockets` chooses a port automatically, it will now use + the same port for IPv4 and IPv6. +* TLS compression is now disabled by default on Python 3.3 and higher + (it is not possible to change this option in older versions. `tornado.options` ~~~~~~~~~~~~~~~~~ @@ -59,6 +77,18 @@ Backwards-compatibility notes * Now works on Python 2.6. +`tornado.simple_httpclient` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Improved default cipher suite selection (Python 2.7+). + +`tornado.speedups` +~~~~~~~~~~~~~~~~~~ + +* The C extension now builds on windows in both 32 and 64-bit modes. +* The fallback mechanism for detecting a missing C compiler now + works correctly on Mac OS X. + `tornado.stack_context` ~~~~~~~~~~~~~~~~~~~~~~~ @@ -72,6 +102,7 @@ Backwards-compatibility notes but were not run with ``@gen_test`` or any similar decorator (this would previously result in the test silently being skipped). * Better stack traces are now displayed when a test times out. +* Fixed the test suite when ``unittest2`` is installed on Python 3. `tornado.web` ~~~~~~~~~~~~~ From 5a1497dfaf8e4252972a41e9e22d077423675437 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sat, 26 Apr 2014 20:13:46 -0400 Subject: [PATCH 21/21] Update docs for changes in 3.3. --- docs/concurrent.rst | 52 +++++++++---------------- docs/ioloop.rst | 1 + docs/releases/next.rst | 12 ++---- docs/releases/v3.2.0.rst | 2 +- tornado/concurrent.py | 61 ++++++++++++++++++++++++++++-- tornado/iostream.py | 82 ++++++++++++++++++++++++++++++---------- tornado/web.py | 5 +++ 7 files changed, 146 insertions(+), 69 deletions(-) diff --git a/docs/concurrent.rst b/docs/concurrent.rst index 378adc9a..051c91ab 100644 --- a/docs/concurrent.rst +++ b/docs/concurrent.rst @@ -3,43 +3,25 @@ .. automodule:: tornado.concurrent :members: + :exclude-members: Future, TracebackFuture - .. py:class:: Future + .. autoclass:: Future - A ``Future`` encapsulates the result of an asynchronous - operation. In synchronous applications ``Futures`` are used - to wait for the result from a thread or process pool; in - Tornado they are normally used with `.IOLoop.add_future` or by - yielding them in a `.gen.coroutine`. + Consumer methods + ^^^^^^^^^^^^^^^^ - If the `concurrent.futures` package is available, - `tornado.concurrent.Future` is simply an alias for - `concurrent.futures.Future`. Otherwise, we support the same - interface with a few limitations: + .. automethod:: Future.result + .. automethod:: Future.exception + .. automethod:: Future.exc_info + .. automethod:: Future.add_done_callback + .. automethod:: Future.done + .. automethod:: Future.running + .. automethod:: Future.cancel + .. automethod:: Future.cancelled - * It is an error to call `result` or `exception` before the - ``Future`` has completed. - * Cancellation is not supported. + Producer methods + ^^^^^^^^^^^^^^^^ - .. py:method:: result() - - If the operation succeeded, return its result. If it failed, - re-raise its exception. - - .. py:method:: exception() - - If the operation raised an exception, return the `Exception` - object. Otherwise returns None. - - .. py:method:: add_done_callback(fn) - - Attaches the given callback to the `Future`. It will be invoked - with the `Future` as its argument when it has finished running - and its result is available. In Tornado consider using - `.IOLoop.add_future` instead of calling `add_done_callback` - directly. - - .. py:method:: done() - - Returns True if the future has finished running and its - `result` and `exception` methods are available. + .. automethod:: Future.set_result + .. automethod:: Future.set_exception + .. automethod:: Future.set_exc_info diff --git a/docs/ioloop.rst b/docs/ioloop.rst index 6978c39c..bd364cc4 100644 --- a/docs/ioloop.rst +++ b/docs/ioloop.rst @@ -16,6 +16,7 @@ .. automethod:: IOLoop.instance .. automethod:: IOLoop.initialized .. automethod:: IOLoop.install + .. automethod:: IOLoop.clear_instance .. automethod:: IOLoop.start .. automethod:: IOLoop.stop .. automethod:: IOLoop.run_sync diff --git a/docs/releases/next.rst b/docs/releases/next.rst index bc3337c0..e08593c6 100644 --- a/docs/releases/next.rst +++ b/docs/releases/next.rst @@ -77,17 +77,11 @@ Backwards-compatibility notes * Now works on Python 2.6. -`tornado.simple_httpclient` -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``tornado.simple_httpclient`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Improved default cipher suite selection (Python 2.7+). -`tornado.speedups` -~~~~~~~~~~~~~~~~~~ - -* The C extension now builds on windows in both 32 and 64-bit modes. -* The fallback mechanism for detecting a missing C compiler now - works correctly on Mac OS X. `tornado.stack_context` ~~~~~~~~~~~~~~~~~~~~~~~ @@ -120,3 +114,5 @@ Backwards-compatibility notes values when the other side closes. * The C speedup module now builds correctly with MSVC, and can support messages larger than 2GB on 64-bit systems. +* The fallback mechanism for detecting a missing C compiler now + works correctly on Mac OS X. diff --git a/docs/releases/v3.2.0.rst b/docs/releases/v3.2.0.rst index 7e5299e9..3e959393 100644 --- a/docs/releases/v3.2.0.rst +++ b/docs/releases/v3.2.0.rst @@ -32,7 +32,7 @@ New modules `tornado.concurrent` ~~~~~~~~~~~~~~~~~~~~ -* `.TracebackFuture` now accepts a ``timeout`` keyword argument (although +* ``TracebackFuture`` now accepts a ``timeout`` keyword argument (although it is still incorrect to use a non-zero timeout in non-blocking code). ``tornado.curl_httpclient`` diff --git a/tornado/concurrent.py b/tornado/concurrent.py index ab7b65c3..3ae2f727 100644 --- a/tornado/concurrent.py +++ b/tornado/concurrent.py @@ -43,8 +43,15 @@ class ReturnValueIgnoredError(Exception): class Future(object): """Placeholder for an asynchronous result. - Similar to `concurrent.futures.Future`, but not thread-safe (and - therefore faster for use with single-threaded event loops. + A ``Future`` encapsulates the result of an asynchronous + operation. In synchronous applications ``Futures`` are used + to wait for the result from a thread or process pool; in + Tornado they are normally used with `.IOLoop.add_future` or by + yielding them in a `.gen.coroutine`. + + `tornado.concurrent.Future` is similar to + `concurrent.futures.Future`, but not thread-safe (and therefore + faster for use with single-threaded event loops). In addition to ``exception`` and ``set_exception``, methods ``exc_info`` and ``set_exc_info`` are supported to capture tracebacks in Python 2. @@ -52,6 +59,14 @@ class Future(object): Python 2 futures backport this information is discarded. This functionality was previously available in a separate class ``TracebackFuture``, which is now a deprecated alias for this class. + + .. versionchanged:: 3.3 + `tornado.concurrent.Future` is always a thread-unsafe ``Future`` + with support for the ``exc_info`` methods. Previously it would + be an alias for the thread-safe `concurrent.futures.Future` + if that package was available and fall back to the thread-unsafe + implementation if it was not. + """ def __init__(self): self._done = False @@ -61,18 +76,33 @@ class Future(object): self._callbacks = [] def cancel(self): + """Cancel the operation, if possible. + + Tornado ``Futures`` do not support cancellation, so this method always + returns False. + """ return False def cancelled(self): + """Returns True if the operation has been cancelled. + + Tornado ``Futures`` do not support cancellation, so this method + always returns False. + """ return False def running(self): + """Returns True if this operation is currently running.""" return not self._done def done(self): + """Returns True if the future has finished running.""" return self._done def result(self, timeout=None): + """If the operation succeeded, return its result. If it failed, + re-raise its exception. + """ if self._result is not None: return self._result if self._exc_info is not None: @@ -83,6 +113,9 @@ class Future(object): return self._result def exception(self, timeout=None): + """If the operation raised an exception, return the `Exception` + object. Otherwise returns None. + """ if self._exception is not None: return self._exception else: @@ -90,25 +123,45 @@ class Future(object): return None def add_done_callback(self, fn): + """Attaches the given callback to the `Future`. + + It will be invoked with the `Future` as its argument when the Future + has finished running and its result is available. In Tornado + consider using `.IOLoop.add_future` instead of calling + `add_done_callback` directly. + """ if self._done: fn(self) else: self._callbacks.append(fn) def set_result(self, result): + """Sets the result of a ``Future``. + + It is undefined to call any of the ``set`` methods more than once + on the same object. + """ self._result = result self._set_done() def set_exception(self, exception): + """Sets the exception of a ``Future.``""" self._exception = exception self._set_done() def exc_info(self): + """Returns a tuple in the same format as `sys.exc_info` or None. + + .. versionadded:: 3.3 + """ return self._exc_info def set_exc_info(self, exc_info): - """Traceback-aware replacement for - `~concurrent.futures.Future.set_exception`. + """Sets the exception information of a ``Future.`` + + Preserves tracebacks on Python 2. + + .. versionadded:: 3.3 """ self._exc_info = exc_info self.set_exception(exc_info[1]) diff --git a/tornado/iostream.py b/tornado/iostream.py index 651a3a89..1376bade 100644 --- a/tornado/iostream.py +++ b/tornado/iostream.py @@ -71,8 +71,13 @@ class BaseIOStream(object): """A utility class to write to and read from a non-blocking file or socket. We support a non-blocking ``write()`` and a family of ``read_*()`` methods. - All of the methods take callbacks (since writing and reading are - non-blocking and asynchronous). + All of the methods take an optional ``callback`` argument and return a + `.Future` only if no callback is given. When the operation completes, + the callback will be run or the `.Future` will resolve with the data + read (or ``None`` for ``write()``). All outstanding ``Futures`` will + resolve with a `StreamClosedError` when the stream is closed; users + of the callback interface will be notified via + `.BaseIOStream.set_close_callback` instead. When a stream is closed due to an error, the IOStream's ``error`` attribute contains the exception object. @@ -147,10 +152,16 @@ class BaseIOStream(object): return None def read_until_regex(self, regex, callback=None): - """Run ``callback`` when we read the given regex pattern. + """Asynchronously read until we have matched the given regex. - The callback will get the data read (including the data that - matched the regex and anything that came before it) as an argument. + The result includes the data that matches the regex and anything + that came before it. If a callback is given, it will be run + with the data as an argument; if not, this method returns a + `.Future`. + + .. versionchanged:: 3.3 + The callback argument is now optional and a `.Future` will + be returned if it is omitted. """ future = self._set_read_callback(callback) self._read_regex = re.compile(regex) @@ -158,10 +169,15 @@ class BaseIOStream(object): return future def read_until(self, delimiter, callback=None): - """Run ``callback`` when we read the given delimiter. + """Asynchronously read until we have found the given delimiter. - The callback will get the data read (including the delimiter) - as an argument. + The result includes all the data read including the delimiter. + If a callback is given, it will be run with the data as an argument; + if not, this method returns a `.Future`. + + .. versionchanged:: 3.3 + The callback argument is now optional and a `.Future` will + be returned if it is omitted. """ future = self._set_read_callback(callback) self._read_delimiter = delimiter @@ -169,12 +185,17 @@ class BaseIOStream(object): return future def read_bytes(self, num_bytes, callback=None, streaming_callback=None): - """Run callback when we read the given number of bytes. + """Asynchronously read a number of bytes. If a ``streaming_callback`` is given, it will be called with chunks - of data as they become available, and the argument to the final - ``callback`` will be empty. Otherwise, the ``callback`` gets - the data as an argument. + of data as they become available, and the final result will be empty. + Otherwise, the result is all the data that was read. + If a callback is given, it will be run with the data as an argument; + if not, this method returns a `.Future`. + + .. versionchanged:: 3.3 + The callback argument is now optional and a `.Future` will + be returned if it is omitted. """ future = self._set_read_callback(callback) assert isinstance(num_bytes, numbers.Integral) @@ -184,15 +205,17 @@ class BaseIOStream(object): return future def read_until_close(self, callback=None, streaming_callback=None): - """Reads all data from the socket until it is closed. + """Asynchronously reads all data from the socket until it is closed. If a ``streaming_callback`` is given, it will be called with chunks - of data as they become available, and the argument to the final - ``callback`` will be empty. Otherwise, the ``callback`` gets the - data as an argument. + of data as they become available, and the final result will be empty. + Otherwise, the result is all the data that was read. + If a callback is given, it will be run with the data as an argument; + if not, this method returns a `.Future`. - Subject to ``max_buffer_size`` limit from `IOStream` constructor if - a ``streaming_callback`` is not used. + .. versionchanged:: 3.3 + The callback argument is now optional and a `.Future` will + be returned if it is omitted. """ future = self._set_read_callback(callback) self._streaming_callback = stack_context.wrap(streaming_callback) @@ -207,12 +230,20 @@ class BaseIOStream(object): return future def write(self, data, callback=None): - """Write the given data to this stream. + """Asynchronously write the given data to this stream. If ``callback`` is given, we call it when all of the buffered write data has been successfully written to the stream. If there was previously buffered write data and an old write callback, that callback is simply overwritten with this new callback. + + If no ``callback`` is given, this method returns a `.Future` that + resolves (with a result of ``None``) when the write has been + completed. If `write` is called again before that `.Future` has + resolved, the previous future will be orphaned and will never resolve. + + .. versionchanged:: 3.3 + Now returns a `.Future` if no callback is given. """ assert isinstance(data, bytes_type) self._check_closed() @@ -238,7 +269,12 @@ class BaseIOStream(object): return future def set_close_callback(self, callback): - """Call the given callback when the stream is closed.""" + """Call the given callback when the stream is closed. + + This is not necessary for applications that use the `.Future` + interface; all outstanding ``Futures`` will resolve with a + `StreamClosedError` when the stream is closed. + """ self._close_callback = stack_context.wrap(callback) def close(self, exc_info=False): @@ -741,7 +777,8 @@ class IOStream(BaseIOStream): not previously connected. The address parameter is in the same format as for `socket.connect `, i.e. a ``(host, port)`` tuple. If ``callback`` is specified, - it will be called when the connection is completed. + it will be called when the connection is completed; if not + this method returns a `.Future`. If specified, the ``server_hostname`` parameter will be used in SSL connections for certificate validation (if requested in @@ -753,6 +790,9 @@ class IOStream(BaseIOStream): which case the data will be written as soon as the connection is ready. Calling `IOStream` read methods before the socket is connected works on some platforms but is non-portable. + + .. versionchanged:: 3.3 + If no callback is given, returns a `.Future`. """ self._connecting = True try: diff --git a/tornado/web.py b/tornado/web.py index e2ef7eef..4b9300fb 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -2299,6 +2299,11 @@ class GZipContentEncoding(OutputTransform): """Applies the gzip content encoding to the response. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 + + .. versionchanged:: 3.3 + Now compresses all mime types beginning with ``text/``, instead + of just a whitelist. (the whitelist is still used for certain + non-text mime types). """ # Whitelist of compressible mime types (in addition to any types # beginning with "text/").