]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
stack_context: Deprecate ExceptionStackContext
authorBen Darnell <ben@bendarnell.com>
Fri, 27 Apr 2018 16:08:23 +0000 (12:08 -0400)
committerBen Darnell <ben@bendarnell.com>
Fri, 27 Apr 2018 17:08:24 +0000 (13:08 -0400)
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.

12 files changed:
tornado/auth.py
tornado/concurrent.py
tornado/stack_context.py
tornado/test/concurrent_test.py
tornado/test/curl_httpclient_test.py
tornado/test/httpclient_test.py
tornado/test/ioloop_test.py
tornado/test/netutil_test.py
tornado/test/testing_test.py
tornado/test/web_test.py
tornado/testing.py
tornado/web.py

index 0069efcb48712ed06bc987d72a31cff8db63c376..36a08dfb0e3aefd8fc494ea6ad50c5c25d334dbd 100644 (file)
@@ -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
index 850766818d34aa067eb3c172956bdb4f99165c61..398aa643c9d2eb3b37ea4bccf139a580cc7210ce 100644 (file)
@@ -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:
index 6b27473565abc1c4edc45aecc099ad2c9e725ec6..a1eca4c7e1de6ac0afa379c8639c8b255f5d5acf 100644 (file)
@@ -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):
index 1df0532cc5eedc75c13f037f03598de9872a1d18..be6840eda8c4cec83355d35a3820291420132ad2 100644 (file)
@@ -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")
index d0cfa979b4210adaa6d9ff7ac31715649f0880af..b7a859522f60e02d810a872b7ee783fed2c5a0bc 100644 (file)
@@ -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)
index 851f126885c49dab616b5c6e9e27f00a07e24ac9..60c8f490d31e230036bb1cf841343a912c26be07 100644 (file)
@@ -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)
 
index 81b655171250a2b9f03c49e168af99bf17b5d83e..646a1d431d982a69a065a74695c64e70f4426fdb 100644 (file)
@@ -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")
index 5c73818531617fbaf69bd200e40b3dd2a2ac7a70..b0c25c333186d5a29629941615c4280268b182fc 100644 (file)
@@ -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()
index eb3445e90be34be6e9540a62c693004a8b4219db..9b47c92bc39fda89df4db4c2ed5cca08e2e1ebb0 100644 (file)
@@ -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):
index a43b4e48046a6176754889f19f3707df7b6a6d50..45072aac35eba114c0db70222c9fbfab7be9e349 100644 (file)
@@ -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
index 04ea3816a40af8c1ed5ab8c3e06264af1a6e75d9..b73faee3940ebb3c320f5f7e73a6de6130011ee4 100644 (file)
@@ -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
index 6693f10c5ef28c7255d2dfffa27771e80b79143b..d7dbdb1cac5ebe0c229bc7b039ff45b33683d940 100644 (file)
@@ -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)