From: Ben Darnell Date: Fri, 27 Apr 2018 16:08:23 +0000 (-0400) Subject: stack_context: Deprecate ExceptionStackContext X-Git-Tag: v5.1.0b1~21^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9545120726f19fe5c9ac6b513620dbcdd31d32ea;p=thirdparty%2Ftornado.git stack_context: Deprecate ExceptionStackContext Take extra care with the deprecation warnings since a few modules need to continue using ExceptionStackContext for backwards compatibility even though it is not needed in most cases and should not generate warnings unless it is relied upon. --- diff --git a/tornado/auth.py b/tornado/auth.py index 0069efcb4..36a08dfb0 100644 --- a/tornado/auth.py +++ b/tornado/auth.py @@ -136,7 +136,7 @@ def _auth_return_future(f): else: future_set_exc_info(future, (typ, value, tb)) return True - with ExceptionStackContext(handle_exception): + with ExceptionStackContext(handle_exception, delay_warning=True): f(*args, **kwargs) return future return wrapper diff --git a/tornado/concurrent.py b/tornado/concurrent.py index 850766818..398aa643c 100644 --- a/tornado/concurrent.py +++ b/tornado/concurrent.py @@ -528,7 +528,7 @@ def return_future(f): future_set_exc_info(future, (typ, value, tb)) return True exc_info = None - with ExceptionStackContext(handle_error): + with ExceptionStackContext(handle_error, delay_warning=True): try: result = f(*args, **kwargs) if result is not None: diff --git a/tornado/stack_context.py b/tornado/stack_context.py index 6b2747356..a1eca4c7e 100644 --- a/tornado/stack_context.py +++ b/tornado/stack_context.py @@ -182,8 +182,20 @@ class ExceptionStackContext(object): If the exception handler returns true, the exception will be consumed and will not be propagated to other exception handlers. + + .. versionadded:: 5.1 + + The ``delay_warning`` argument can be used to delay the emission + of DeprecationWarnings until an exception is caught by the + ``ExceptionStackContext``, which facilitates certain transitional + use cases. """ - def __init__(self, exception_handler): + def __init__(self, exception_handler, delay_warning=False): + self.delay_warning = delay_warning + if not self.delay_warning: + warnings.warn( + "StackContext is deprecated and will be removed in Tornado 6.0", + DeprecationWarning) self.exception_handler = exception_handler self.active = True @@ -192,6 +204,10 @@ class ExceptionStackContext(object): def exit(self, type, value, traceback): if type is not None: + if self.delay_warning: + warnings.warn( + "StackContext is deprecated and will be removed in Tornado 6.0", + DeprecationWarning) return self.exception_handler(type, value, traceback) def __enter__(self): diff --git a/tornado/test/concurrent_test.py b/tornado/test/concurrent_test.py index 1df0532cc..be6840eda 100644 --- a/tornado/test/concurrent_test.py +++ b/tornado/test/concurrent_test.py @@ -143,8 +143,9 @@ class ReturnFutureTest(AsyncTestCase): def test_delayed_failure(self): future = self.delayed_failure() - self.io_loop.add_future(future, self.stop) - future2 = self.wait() + with ignore_deprecation(): + self.io_loop.add_future(future, self.stop) + future2 = self.wait() self.assertIs(future, future2) with self.assertRaises(ZeroDivisionError): future.result() @@ -362,7 +363,7 @@ class ClientTestMixin(object): def test_callback_error(self): with ignore_deprecation(): self.client.capitalize("HELLO", callback=self.stop) - self.assertRaisesRegexp(CapError, "already capitalized", self.wait) + self.assertRaisesRegexp(CapError, "already capitalized", self.wait) def test_future(self): future = self.client.capitalize("hello") diff --git a/tornado/test/curl_httpclient_test.py b/tornado/test/curl_httpclient_test.py index d0cfa979b..b7a859522 100644 --- a/tornado/test/curl_httpclient_test.py +++ b/tornado/test/curl_httpclient_test.py @@ -108,9 +108,10 @@ class CurlHTTPClientTestCase(AsyncHTTPTestCase): error_event.set() return True - with ExceptionStackContext(error_handler): - request = HTTPRequest(self.get_url('/custom_reason'), - prepare_curl_callback=lambda curl: 1 / 0) + with ignore_deprecation(): + with ExceptionStackContext(error_handler): + request = HTTPRequest(self.get_url('/custom_reason'), + prepare_curl_callback=lambda curl: 1 / 0) yield [error_event.wait(), self.http_client.fetch(request)] self.assertEqual(1, len(exc_info)) self.assertIs(exc_info[0][0], ZeroDivisionError) diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py index 851f12688..60c8f490d 100644 --- a/tornado/test/httpclient_test.py +++ b/tornado/test/httpclient_test.py @@ -228,8 +228,9 @@ Transfer-Encoding: chunked if chunk == b'qwer': 1 / 0 - with ExceptionStackContext(error_handler): - self.fetch('/chunk', streaming_callback=streaming_cb) + with ignore_deprecation(): + with ExceptionStackContext(error_handler): + self.fetch('/chunk', streaming_callback=streaming_cb) self.assertEqual(chunks, [b'asdf', b'qwer']) self.assertEqual(1, len(exc_info)) @@ -344,8 +345,9 @@ Transfer-Encoding: chunked if header_line.lower().startswith('content-type:'): 1 / 0 - with ExceptionStackContext(error_handler): - self.fetch('/chunk', header_callback=header_callback) + with ignore_deprecation(): + with ExceptionStackContext(error_handler): + self.fetch('/chunk', header_callback=header_callback) self.assertEqual(len(exc_info), 1) self.assertIs(exc_info[0][0], ZeroDivisionError) diff --git a/tornado/test/ioloop_test.py b/tornado/test/ioloop_test.py index 81b655171..646a1d431 100644 --- a/tornado/test/ioloop_test.py +++ b/tornado/test/ioloop_test.py @@ -417,17 +417,18 @@ class TestIOLoop(AsyncTestCase): self.wait() def test_spawn_callback(self): - # An added callback runs in the test's stack_context, so will be - # re-raised in wait(). - self.io_loop.add_callback(lambda: 1 / 0) - with self.assertRaises(ZeroDivisionError): - self.wait() - # A spawned callback is run directly on the IOLoop, so it will be - # logged without stopping the test. - self.io_loop.spawn_callback(lambda: 1 / 0) - self.io_loop.add_callback(self.stop) - with ExpectLog(app_log, "Exception in callback"): - self.wait() + with ignore_deprecation(): + # An added callback runs in the test's stack_context, so will be + # re-raised in wait(). + self.io_loop.add_callback(lambda: 1 / 0) + with self.assertRaises(ZeroDivisionError): + self.wait() + # A spawned callback is run directly on the IOLoop, so it will be + # logged without stopping the test. + self.io_loop.spawn_callback(lambda: 1 / 0) + self.io_loop.add_callback(self.stop) + with ExpectLog(app_log, "Exception in callback"): + self.wait() @skipIfNonUnix def test_remove_handler_from_handler(self): @@ -605,11 +606,12 @@ class TestIOLoopFutures(AsyncTestCase): # stack_context propagates to the ioloop callback, but the worker # task just has its exceptions caught and saved in the Future. - with futures.ThreadPoolExecutor(1) as pool: - with ExceptionStackContext(handle_exception): - self.io_loop.add_future(pool.submit(task), callback) - ready.set() - self.wait() + with ignore_deprecation(): + with futures.ThreadPoolExecutor(1) as pool: + with ExceptionStackContext(handle_exception): + self.io_loop.add_future(pool.submit(task), callback) + ready.set() + self.wait() self.assertEqual(self.exception.args[0], "callback") self.assertEqual(self.future.exception().args[0], "worker") diff --git a/tornado/test/netutil_test.py b/tornado/test/netutil_test.py index 5c7381853..b0c25c333 100644 --- a/tornado/test/netutil_test.py +++ b/tornado/test/netutil_test.py @@ -59,8 +59,8 @@ class _ResolverErrorTestMixin(object): self.stop(exc_val) return True # Halt propagation. - with ExceptionStackContext(handler): - with ignore_deprecation(): + with ignore_deprecation(): + with ExceptionStackContext(handler): self.resolver.resolve('an invalid domain', 80, callback=self.stop) result = self.wait() diff --git a/tornado/test/testing_test.py b/tornado/test/testing_test.py index eb3445e90..9b47c92bc 100644 --- a/tornado/test/testing_test.py +++ b/tornado/test/testing_test.py @@ -33,12 +33,13 @@ def set_environ(name, value): class AsyncTestCaseTest(AsyncTestCase): def test_exception_in_callback(self): - self.io_loop.add_callback(lambda: 1 / 0) - try: - self.wait() - self.fail("did not get expected exception") - except ZeroDivisionError: - pass + with ignore_deprecation(): + self.io_loop.add_callback(lambda: 1 / 0) + try: + self.wait() + self.fail("did not get expected exception") + except ZeroDivisionError: + pass def test_wait_timeout(self): time = self.io_loop.time @@ -69,15 +70,16 @@ class AsyncTestCaseTest(AsyncTestCase): self.wait(timeout=0.15) def test_multiple_errors(self): - def fail(message): - raise Exception(message) - self.io_loop.add_callback(lambda: fail("error one")) - self.io_loop.add_callback(lambda: fail("error two")) - # The first error gets raised; the second gets logged. - with ExpectLog(app_log, "multiple unhandled exceptions"): - with self.assertRaises(Exception) as cm: - self.wait() - self.assertEqual(str(cm.exception), "error one") + with ignore_deprecation(): + def fail(message): + raise Exception(message) + self.io_loop.add_callback(lambda: fail("error one")) + self.io_loop.add_callback(lambda: fail("error two")) + # The first error gets raised; the second gets logged. + with ExpectLog(app_log, "multiple unhandled exceptions"): + with self.assertRaises(Exception) as cm: + self.wait() + self.assertEqual(str(cm.exception), "error one") class AsyncHTTPTestCaseTest(AsyncHTTPTestCase): diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index a43b4e480..45072aac3 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -1821,18 +1821,19 @@ class MultipleExceptionTest(SimpleHandlerTestCase): MultipleExceptionTest.Handler.exc_count += 1 def test_multi_exception(self): - # This test verifies that multiple exceptions raised into the same - # ExceptionStackContext do not generate extraneous log entries - # due to "Cannot send error response after headers written". - # log_exception is called, but it does not proceed to send_error. - response = self.fetch('/') - self.assertEqual(response.code, 500) - response = self.fetch('/') - self.assertEqual(response.code, 500) - # Each of our two requests generated two exceptions, we should have - # seen at least three of them by now (the fourth may still be - # in the queue). - self.assertGreater(MultipleExceptionTest.Handler.exc_count, 2) + with ignore_deprecation(): + # This test verifies that multiple exceptions raised into the same + # ExceptionStackContext do not generate extraneous log entries + # due to "Cannot send error response after headers written". + # log_exception is called, but it does not proceed to send_error. + response = self.fetch('/') + self.assertEqual(response.code, 500) + response = self.fetch('/') + self.assertEqual(response.code, 500) + # Each of our two requests generated two exceptions, we should have + # seen at least three of them by now (the fourth may still be + # in the queue). + self.assertGreater(MultipleExceptionTest.Handler.exc_count, 2) @wsgi_safe diff --git a/tornado/testing.py b/tornado/testing.py index 04ea3816a..b73faee39 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -265,7 +265,7 @@ class AsyncTestCase(unittest.TestCase): raise_exc_info(failure) def run(self, result=None): - with ExceptionStackContext(self._handle_exception): + with ExceptionStackContext(self._handle_exception, delay_warning=True): super(AsyncTestCase, self).run(result) # As a last resort, if an exception escaped super.run() and wasn't # re-raised in tearDown, raise it here. This will cause the diff --git a/tornado/web.py b/tornado/web.py index 6693f10c5..d7dbdb1ca 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -1718,7 +1718,7 @@ def asynchronous(method): def wrapper(self, *args, **kwargs): self._auto_finish = False with stack_context.ExceptionStackContext( - self._stack_context_handle_exception): + self._stack_context_handle_exception, delay_warning=True): result = method(self, *args, **kwargs) if result is not None: result = gen.convert_yielded(result)