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
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:
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
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):
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()
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")
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)
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))
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)
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):
# 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")
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()
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
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):
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
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
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)