From 0e43388cae1042353cfbf8378bef608586b17e78 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sun, 18 Mar 2018 18:56:29 -0400 Subject: [PATCH] gen: Deprecate gen.engine --- tornado/gen.py | 8 +++ tornado/test/auth_test.py | 81 ++++++++++-------------------- tornado/test/concurrent_test.py | 44 ++++++++++------ tornado/test/gen_test.py | 60 ++++++++++++---------- tornado/test/stack_context_test.py | 38 +++++++------- tornado/test/web_test.py | 3 +- 6 files changed, 119 insertions(+), 115 deletions(-) diff --git a/tornado/gen.py b/tornado/gen.py index ef70374a..d252c5a0 100644 --- a/tornado/gen.py +++ b/tornado/gen.py @@ -96,6 +96,7 @@ import itertools import os import sys import types +import warnings from tornado.concurrent import (Future, is_future, chain_future, future_set_exc_info, future_add_done_callback, future_set_result_unless_cancelled) @@ -213,7 +214,14 @@ def engine(func): they are finished. One notable exception is the `~tornado.web.RequestHandler` :ref:`HTTP verb methods `, which use ``self.finish()`` in place of a callback argument. + + .. deprecated:: 5.1 + + This decorator will be removed in 6.0. Use `coroutine` or + ``async def`` instead. """ + warnings.warn("gen.engine is deprecated, use gen.coroutine or async def instead", + DeprecationWarning) func = _make_coroutine_wrapper(func, replace_callback=False) @functools.wraps(func) diff --git a/tornado/test/auth_test.py b/tornado/test/auth_test.py index 704ab6c0..0d26a35a 100644 --- a/tornado/test/auth_test.py +++ b/tornado/test/auth_test.py @@ -252,16 +252,17 @@ class TwitterClientLoginHandler(TwitterClientHandler): class TwitterClientLoginGenEngineHandler(TwitterClientHandler): - @asynchronous - @gen.engine - def get(self): - if self.get_argument("oauth_token", None): - user = yield self.get_authenticated_user() - self.finish(user) - else: - # Old style: with @gen.engine we can ignore the Future from - # authorize_redirect. - self.authorize_redirect() + with ignore_deprecation(): + @asynchronous + @gen.engine + def get(self): + if self.get_argument("oauth_token", None): + user = yield self.get_authenticated_user() + self.finish(user) + else: + # Old style: with @gen.engine we can ignore the Future from + # authorize_redirect. + self.authorize_redirect() class TwitterClientLoginGenCoroutineHandler(TwitterClientHandler): @@ -277,21 +278,22 @@ class TwitterClientLoginGenCoroutineHandler(TwitterClientHandler): class TwitterClientShowUserHandlerLegacy(TwitterClientHandler): - @asynchronous - @gen.engine - def get(self): - # TODO: would be nice to go through the login flow instead of - # cheating with a hard-coded access token. - with warnings.catch_warnings(): - warnings.simplefilter('ignore', DeprecationWarning) - response = yield gen.Task(self.twitter_request, - '/users/show/%s' % self.get_argument('name'), - access_token=dict(key='hjkl', secret='vbnm')) - if response is None: - self.set_status(500) - self.finish('error from twitter request') - else: - self.finish(response) + with ignore_deprecation(): + @asynchronous + @gen.engine + def get(self): + # TODO: would be nice to go through the login flow instead of + # cheating with a hard-coded access token. + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + response = yield gen.Task(self.twitter_request, + '/users/show/%s' % self.get_argument('name'), + access_token=dict(key='hjkl', secret='vbnm')) + if response is None: + self.set_status(500) + self.finish('error from twitter request') + else: + self.finish(response) class TwitterClientShowUserHandler(TwitterClientHandler): @@ -310,22 +312,6 @@ class TwitterClientShowUserHandler(TwitterClientHandler): self.finish(response) -class TwitterClientShowUserFutureHandler(TwitterClientHandler): - @asynchronous - @gen.engine - def get(self): - try: - response = yield self.twitter_request( - '/users/show/%s' % self.get_argument('name'), - access_token=dict(key='hjkl', secret='vbnm')) - except AuthError as e: - self.set_status(500) - self.finish(str(e)) - return - assert response is not None - self.finish(response) - - class TwitterServerAccessTokenHandler(RequestHandler): def get(self): self.write('oauth_token=hjkl&oauth_token_secret=vbnm&screen_name=foo') @@ -395,8 +381,6 @@ class AuthTest(AsyncHTTPTestCase): TwitterClientShowUserHandlerLegacy, dict(test=self)), ('/twitter/client/show_user', TwitterClientShowUserHandler, dict(test=self)), - ('/twitter/client/show_user_future', - TwitterClientShowUserFutureHandler, dict(test=self)), # simulated servers ('/openid/server/authenticate', OpenIdServerAuthenticateHandler), @@ -623,17 +607,6 @@ class AuthTest(AsyncHTTPTestCase): self.assertEqual(response.code, 500) self.assertEqual(response.body, b'error from twitter request') - def test_twitter_show_user_future(self): - response = self.fetch('/twitter/client/show_user_future?name=somebody') - response.rethrow() - self.assertEqual(json_decode(response.body), - {'name': 'Somebody', 'screen_name': 'somebody'}) - - def test_twitter_show_user_future_error(self): - response = self.fetch('/twitter/client/show_user_future?name=error') - self.assertEqual(response.code, 500) - self.assertIn(b'Error response HTTP 500', response.body) - class GoogleLoginHandler(RequestHandler, GoogleOAuth2Mixin): def initialize(self, test): diff --git a/tornado/test/concurrent_test.py b/tornado/test/concurrent_test.py index dca66547..5199033a 100644 --- a/tornado/test/concurrent_test.py +++ b/tornado/test/concurrent_test.py @@ -177,11 +177,30 @@ class ReturnFutureTest(AsyncTestCase): self.assertIs(result, None) future.result() + @gen_test + def test_future_traceback_legacy(self): + with ignore_deprecation(): + @return_future + @gen.engine + def f(callback): + yield gen.Task(self.io_loop.add_callback) + try: + 1 / 0 + except ZeroDivisionError: + self.expected_frame = traceback.extract_tb( + sys.exc_info()[2], limit=1)[0] + raise + try: + yield f() + self.fail("didn't get expected exception") + except ZeroDivisionError: + tb = traceback.extract_tb(sys.exc_info()[2]) + self.assertIn(self.expected_frame, tb) + @gen_test def test_future_traceback(self): - @return_future - @gen.engine - def f(callback): + @gen.coroutine + def f(): yield gen.Task(self.io_loop.add_callback) try: 1 / 0 @@ -311,9 +330,8 @@ class DecoratorCapClient(BaseCapClient): class GeneratorCapClient(BaseCapClient): - @return_future - @gen.engine - def capitalize(self, request_data, callback): + @gen.coroutine + def capitalize(self, request_data): logging.debug('capitalize') stream = IOStream(socket.socket()) logging.debug('connecting') @@ -323,7 +341,7 @@ class GeneratorCapClient(BaseCapClient): data = yield gen.Task(stream.read_until, b'\n') logging.debug('returning') stream.close() - callback(self.process_response(data)) + raise gen.Return(self.process_response(data)) class ClientTestMixin(object): @@ -362,22 +380,18 @@ class ClientTestMixin(object): self.assertRaisesRegexp(CapError, "already capitalized", future.result) def test_generator(self): - @gen.engine + @gen.coroutine def f(): result = yield self.client.capitalize("hello") self.assertEqual(result, "HELLO") - self.stop() - f() - self.wait() + self.io_loop.run_sync(f) def test_generator_error(self): - @gen.engine + @gen.coroutine def f(): with self.assertRaisesRegexp(CapError, "already capitalized"): yield self.client.capitalize("HELLO") - self.stop() - f() - self.wait() + self.io_loop.run_sync(f) class ManualClientTest(ClientTestMixin, AsyncTestCase): diff --git a/tornado/test/gen_test.py b/tornado/test/gen_test.py index 12621cec..6c75b5a2 100644 --- a/tornado/test/gen_test.py +++ b/tornado/test/gen_test.py @@ -9,6 +9,7 @@ import sys import textwrap import time import weakref +import warnings from tornado.concurrent import return_future, Future from tornado.escape import url_escape @@ -17,7 +18,7 @@ from tornado.ioloop import IOLoop from tornado.log import app_log from tornado import stack_context from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, ExpectLog, gen_test -from tornado.test.util import unittest, skipOnTravis, skipBefore33, skipBefore35, skipNotCPython, exec_test # noqa: E501 +from tornado.test.util import unittest, skipOnTravis, skipBefore33, skipBefore35, skipNotCPython, exec_test, ignore_deprecation # noqa: E501 from tornado.web import Application, RequestHandler, asynchronous, HTTPError from tornado import gen @@ -35,9 +36,16 @@ except ImportError: class GenEngineTest(AsyncTestCase): def setUp(self): + self.warning_catcher = warnings.catch_warnings() + self.warning_catcher.__enter__() + warnings.simplefilter('ignore', DeprecationWarning) super(GenEngineTest, self).setUp() self.named_contexts = [] + def tearDown(self): + super(GenEngineTest, self).tearDown() + self.warning_catcher.__exit__(None, None, None) + def named_context(self, name): @contextlib.contextmanager def context(): @@ -1085,20 +1093,21 @@ class GenCoroutineTest(AsyncTestCase): class GenSequenceHandler(RequestHandler): - @asynchronous - @gen.engine - def get(self): - self.io_loop = self.request.connection.stream.io_loop - self.io_loop.add_callback((yield gen.Callback("k1"))) - yield gen.Wait("k1") - self.write("1") - self.io_loop.add_callback((yield gen.Callback("k2"))) - yield gen.Wait("k2") - self.write("2") - # reuse an old key - self.io_loop.add_callback((yield gen.Callback("k1"))) - yield gen.Wait("k1") - self.finish("3") + with ignore_deprecation(): + @asynchronous + @gen.engine + def get(self): + self.io_loop = self.request.connection.stream.io_loop + self.io_loop.add_callback((yield gen.Callback("k1"))) + yield gen.Wait("k1") + self.write("1") + self.io_loop.add_callback((yield gen.Callback("k2"))) + yield gen.Wait("k2") + self.write("2") + # reuse an old key + self.io_loop.add_callback((yield gen.Callback("k1"))) + yield gen.Wait("k1") + self.finish("3") class GenCoroutineSequenceHandler(RequestHandler): @@ -1136,8 +1145,7 @@ class GenCoroutineUnfinishedSequenceHandler(RequestHandler): class GenTaskHandler(RequestHandler): - @asynchronous - @gen.engine + @gen.coroutine def get(self): client = AsyncHTTPClient() response = yield gen.Task(client.fetch, self.get_argument('url')) @@ -1146,13 +1154,14 @@ class GenTaskHandler(RequestHandler): class GenExceptionHandler(RequestHandler): - @asynchronous - @gen.engine - def get(self): - # This test depends on the order of the two decorators. - io_loop = self.request.connection.stream.io_loop - yield gen.Task(io_loop.add_callback) - raise Exception("oops") + with ignore_deprecation(): + @asynchronous + @gen.engine + def get(self): + # This test depends on the order of the two decorators. + io_loop = self.request.connection.stream.io_loop + yield gen.Task(io_loop.add_callback) + raise Exception("oops") class GenCoroutineExceptionHandler(RequestHandler): @@ -1165,8 +1174,7 @@ class GenCoroutineExceptionHandler(RequestHandler): class GenYieldExceptionHandler(RequestHandler): - @asynchronous - @gen.engine + @gen.coroutine def get(self): io_loop = self.request.connection.stream.io_loop # Test the interaction of the two stack_contexts. diff --git a/tornado/test/stack_context_test.py b/tornado/test/stack_context_test.py index 098f0f0f..7ddd818a 100644 --- a/tornado/test/stack_context_test.py +++ b/tornado/test/stack_context_test.py @@ -6,7 +6,7 @@ from tornado.log import app_log from tornado.stack_context import (StackContext, wrap, NullContext, StackContextInconsistentError, ExceptionStackContext, run_with_stack_context, _state) from tornado.testing import AsyncHTTPTestCase, AsyncTestCase, ExpectLog, gen_test -from tornado.test.util import unittest +from tornado.test.util import unittest, ignore_deprecation from tornado.web import asynchronous, Application, RequestHandler import contextlib import functools @@ -215,15 +215,16 @@ class StackContextTest(AsyncTestCase): self.wait() def test_yield_in_with(self): - @gen.engine - def f(): - self.callback = yield gen.Callback('a') - with StackContext(functools.partial(self.context, 'c1')): - # This yield is a problem: the generator will be suspended - # and the StackContext's __exit__ is not called yet, so - # the context will be left on _state.contexts for anything - # that runs before the yield resolves. - yield gen.Wait('a') + with ignore_deprecation(): + @gen.engine + def f(): + self.callback = yield gen.Callback('a') + with StackContext(functools.partial(self.context, 'c1')): + # This yield is a problem: the generator will be suspended + # and the StackContext's __exit__ is not called yet, so + # the context will be left on _state.contexts for anything + # that runs before the yield resolves. + yield gen.Wait('a') with self.assertRaises(StackContextInconsistentError): f() @@ -244,14 +245,15 @@ class StackContextTest(AsyncTestCase): def test_yield_in_with_exception_stack_context(self): # As above, but with ExceptionStackContext instead of StackContext. - @gen.engine - def f(): - with ExceptionStackContext(lambda t, v, tb: False): - yield gen.Task(self.io_loop.add_callback) - - with self.assertRaises(StackContextInconsistentError): - f() - self.wait() + with ignore_deprecation(): + @gen.engine + def f(): + with ExceptionStackContext(lambda t, v, tb: False): + yield gen.Task(self.io_loop.add_callback) + + with self.assertRaises(StackContextInconsistentError): + f() + self.wait() @gen_test def test_yield_outside_with_exception_stack_context(self): diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index 39347deb..6aa06266 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -582,8 +582,7 @@ class RedirectHandler(RequestHandler): class EmptyFlushCallbackHandler(RequestHandler): - @asynchronous - @gen.engine + @gen.coroutine def get(self): # Ensure that the flush callback is run whether or not there # was any output. The gen.Task and direct yield forms are -- GitLab