diff --git a/tornado/auth.py b/tornado/auth.py index 8462548fec60c9082d92a46e1e741fcb193c2282..beee6453b4539a1427b90ad75d43640527dec0ec 100644 --- a/tornado/auth.py +++ b/tornado/auth.py @@ -73,6 +73,7 @@ import hashlib import hmac import time import uuid +import warnings from tornado.concurrent import (Future, return_future, chain_future, future_set_exc_info, @@ -113,6 +114,9 @@ def _auth_return_future(f): Note that when using this decorator the ``callback`` parameter inside the function will actually be a future. + + .. deprecated:: 5.1 + Will be removed in 6.0. """ replacer = ArgReplacer(f, 'callback') @@ -121,6 +125,8 @@ def _auth_return_future(f): future = Future() callback, args, kwargs = replacer.replace(future, args, kwargs) if callback is not None: + warnings.warn("callback arguments are deprecated, use the returned Future instead", + DeprecationWarning) future.add_done_callback( functools.partial(_auth_future_to_callback, callback)) @@ -162,6 +168,11 @@ class OpenIdMixin(object): not strictly necessary as this method is synchronous, but they are supplied for consistency with `OAuthMixin.authorize_redirect`. + + .. deprecated:: 5.1 + + The ``callback`` argument and returned awaitable will be removed + in Tornado 6.0; this will be an ordinary synchronous function. """ callback_uri = callback_uri or self.request.uri args = self._openid_args(callback_uri, ax_attrs=ax_attrs) @@ -179,6 +190,11 @@ class OpenIdMixin(object): is present and `authenticate_redirect` if it is not). The result of this method will generally be used to set a cookie. + + .. deprecated:: 5.1 + + The ``callback`` argument is deprecated and will be removed in 6.0. + Use the returned awaitable object instead. """ # Verify the OpenID response via direct request to the OP args = dict((k, v[-1]) for k, v in self.request.arguments.items()) @@ -329,25 +345,29 @@ class OAuthMixin(object): """Redirects the user to obtain OAuth authorization for this service. The ``callback_uri`` may be omitted if you have previously - registered a callback URI with the third-party service. For - some services (including Friendfeed), you must use a - previously-registered callback URI and cannot specify a - callback via this method. + registered a callback URI with the third-party service. For + some services, you must use a previously-registered callback + URI and cannot specify a callback via this method. This method sets a cookie called ``_oauth_request_token`` which is subsequently used (and cleared) in `get_authenticated_user` for security purposes. - Note that this method is asynchronous, although it calls - `.RequestHandler.finish` for you so it may not be necessary - to pass a callback or use the `.Future` it returns. However, - if this method is called from a function decorated with - `.gen.coroutine`, you must call it with ``yield`` to keep the - response from being closed prematurely. + This method is asynchronous and must be called with ``await`` + or ``yield`` (This is different from other ``auth*_redirect`` + methods defined in this module). It calls + `.RequestHandler.finish` for you so you should not write any + other response after it returns. .. versionchanged:: 3.1 Now returns a `.Future` and takes an optional callback, for compatibility with `.gen.coroutine`. + + .. deprecated:: 5.1 + + The ``callback`` argument is deprecated and will be removed in 6.0. + Use the returned awaitable object instead. + """ if callback_uri and getattr(self, "_OAUTH_NO_CALLBACKS", False): raise Exception("This service does not support oauth_callback") @@ -381,6 +401,11 @@ class OAuthMixin(object): requests to this service on behalf of the user. The dictionary will also contain other fields such as ``name``, depending on the service used. + + .. deprecated:: 5.1 + + The ``callback`` argument is deprecated and will be removed in 6.0. + Use the returned awaitable object instead. """ future = callback request_key = escape.utf8(self.get_argument("oauth_token")) @@ -479,7 +504,9 @@ class OAuthMixin(object): return access_token = _oauth_parse_response(response.body) - self._oauth_get_user_future(access_token).add_done_callback( + fut = self._oauth_get_user_future(access_token) + fut = gen.convert_yielded(fut) + fut.add_done_callback( functools.partial(self._on_oauth_get_user, access_token, future)) def _oauth_consumer_token(self): @@ -504,7 +531,18 @@ class OAuthMixin(object): For backwards compatibility, the callback-based ``_oauth_get_user`` method is also supported. + + .. versionchanged:: 5.1 + + Subclasses may also define this method with ``async def``. + + .. deprecated:: 5.1 + + The ``_oauth_get_user`` fallback is deprecated and support for it + will be removed in 6.0. """ + warnings.warn("_oauth_get_user is deprecated, override _oauth_get_user_future instead", + DeprecationWarning) # By default, call the old-style _oauth_get_user, but new code # should override this method instead. self._oauth_get_user(access_token, callback) @@ -588,6 +626,11 @@ class OAuth2Mixin(object): not strictly necessary as this method is synchronous, but they are supplied for consistency with `OAuthMixin.authorize_redirect`. + + .. deprecated:: 5.1 + + The ``callback`` argument and returned awaitable will be removed + in Tornado 6.0; this will be an ordinary synchronous function. """ args = { "redirect_uri": redirect_uri, @@ -648,6 +691,11 @@ class OAuth2Mixin(object): :hide: .. versionadded:: 4.3 + + .. deprecated:: 5.1 + + The ``callback`` argument is deprecated and will be removed in 6.0. + Use the returned awaitable object instead. """ all_args = {} if access_token: @@ -734,6 +782,11 @@ class TwitterMixin(OAuthMixin): .. versionchanged:: 3.1 Now returns a `.Future` and takes an optional callback, for compatibility with `.gen.coroutine`. + + .. deprecated:: 5.1 + + The ``callback`` argument is deprecated and will be removed in 6.0. + Use the returned awaitable object instead. """ http = self.get_auth_http_client() http.fetch(self._oauth_request_token_url(callback_uri=callback_uri), @@ -781,6 +834,10 @@ class TwitterMixin(OAuthMixin): .. testoutput:: :hide: + .. deprecated:: 5.1 + + The ``callback`` argument is deprecated and will be removed in 6.0. + Use the returned awaitable object instead. """ if path.startswith('http:') or path.startswith('https:'): # Raw urls are useful for e.g. search which doesn't follow the @@ -896,6 +953,10 @@ class GoogleOAuth2Mixin(OAuth2Mixin): .. testoutput:: :hide: + .. deprecated:: 5.1 + + The ``callback`` argument is deprecated and will be removed in 6.0. + Use the returned awaitable object instead. """ # noqa: E501 http = self.get_auth_http_client() body = urllib_parse.urlencode({ @@ -973,6 +1034,11 @@ class FacebookGraphMixin(OAuth2Mixin): .. versionchanged:: 4.5 The ``session_expires`` field was updated to support changes made to the Facebook API in March 2017. + + .. deprecated:: 5.1 + + The ``callback`` argument is deprecated and will be removed in 6.0. + Use the returned awaitable object instead. """ http = self.get_auth_http_client() args = { @@ -991,6 +1057,7 @@ class FacebookGraphMixin(OAuth2Mixin): functools.partial(self._on_access_token, redirect_uri, client_id, client_secret, callback, fields)) + @gen.coroutine def _on_access_token(self, redirect_uri, client_id, client_secret, future, fields, response): if response.error: @@ -1003,10 +1070,8 @@ class FacebookGraphMixin(OAuth2Mixin): "expires_in": args.get("expires_in") } - self.facebook_request( + user = yield self.facebook_request( path="/me", - callback=functools.partial( - self._on_get_user_info, future, session, fields), access_token=session["access_token"], appsecret_proof=hmac.new(key=client_secret.encode('utf8'), msg=session["access_token"].encode('utf8'), @@ -1014,7 +1079,6 @@ class FacebookGraphMixin(OAuth2Mixin): fields=",".join(fields) ) - def _on_get_user_info(self, future, session, fields, user): if user is None: future_set_result_unless_cancelled(future, None) return @@ -1050,7 +1114,7 @@ class FacebookGraphMixin(OAuth2Mixin): Example usage: - ..testcode:: + .. testcode:: class MainHandler(tornado.web.RequestHandler, tornado.auth.FacebookGraphMixin): @@ -1080,6 +1144,11 @@ class FacebookGraphMixin(OAuth2Mixin): .. versionchanged:: 3.1 Added the ability to override ``self._FACEBOOK_BASE_URL``. + + .. deprecated:: 5.1 + + The ``callback`` argument is deprecated and will be removed in 6.0. + Use the returned awaitable object instead. """ url = self._FACEBOOK_BASE_URL + path # Thanks to the _auth_return_future decorator, our "callback" diff --git a/tornado/concurrent.py b/tornado/concurrent.py index aa38bb9968a21d0f713573690638970f264c574b..2245c0bd40d99519853ecef646d38017a8d60dcb 100644 --- a/tornado/concurrent.py +++ b/tornado/concurrent.py @@ -33,6 +33,7 @@ import platform import textwrap import traceback import sys +import warnings from tornado.log import app_log from tornado.stack_context import ExceptionStackContext, wrap @@ -496,6 +497,15 @@ def return_future(f): same function, provided ``@return_future`` appears first. However, consider using ``@gen.coroutine`` instead of this combination. + .. versionchanged:: 5.1 + + Now raises a `.DeprecationWarning` if a callback argument is passed to + the decorated function and deprecation warnings are enabled. + + .. deprecated:: 5.1 + + New code should use coroutines directly instead of wrapping + callback-based code with this decorator. """ replacer = ArgReplacer(f, 'callback') @@ -533,6 +543,9 @@ def return_future(f): # immediate exception, and again when the future resolves and # the callback triggers its exception by calling future.result()). if callback is not None: + warnings.warn("callback arguments are deprecated, use the returned Future instead", + DeprecationWarning) + def run_callback(future): result = future.result() if result is _NO_RESULT: diff --git a/tornado/test/auth_test.py b/tornado/test/auth_test.py index d79c365a8d0be814b3b1f738fbadbf5991e1faba..704ab6c0659814a13af91102a9f1df0d397f5eb3 100644 --- a/tornado/test/auth_test.py +++ b/tornado/test/auth_test.py @@ -6,6 +6,8 @@ from __future__ import absolute_import, division, print_function +import warnings + from tornado.auth import ( AuthError, OpenIdMixin, OAuthMixin, OAuth2Mixin, GoogleOAuth2Mixin, FacebookGraphMixin, TwitterMixin, @@ -16,19 +18,22 @@ from tornado import gen from tornado.httputil import url_concat from tornado.log import gen_log from tornado.testing import AsyncHTTPTestCase, ExpectLog +from tornado.test.util import ignore_deprecation from tornado.web import RequestHandler, Application, asynchronous, HTTPError -class OpenIdClientLoginHandler(RequestHandler, OpenIdMixin): +class OpenIdClientLoginHandlerLegacy(RequestHandler, OpenIdMixin): def initialize(self, test): self._OPENID_ENDPOINT = test.get_url('/openid/server/authenticate') @asynchronous def get(self): if self.get_argument('openid.mode', None): - self.get_authenticated_user( - self.on_user, http_client=self.settings['http_client']) - return + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + self.get_authenticated_user( + self.on_user, http_client=self.settings['http_client']) + return res = self.authenticate_redirect() assert isinstance(res, Future) assert res.done() @@ -39,6 +44,23 @@ class OpenIdClientLoginHandler(RequestHandler, OpenIdMixin): self.finish(user) +class OpenIdClientLoginHandler(RequestHandler, OpenIdMixin): + def initialize(self, test): + self._OPENID_ENDPOINT = test.get_url('/openid/server/authenticate') + + @gen.coroutine + def get(self): + if self.get_argument('openid.mode', None): + user = yield self.get_authenticated_user(http_client=self.settings['http_client']) + if user is None: + raise Exception("user is None") + self.finish(user) + return + res = self.authenticate_redirect() + assert isinstance(res, Future) + assert res.done() + + class OpenIdServerAuthenticateHandler(RequestHandler): def post(self): if self.get_argument('openid.mode') != 'check_authentication': @@ -46,7 +68,7 @@ class OpenIdServerAuthenticateHandler(RequestHandler): self.write('is_valid:true') -class OAuth1ClientLoginHandler(RequestHandler, OAuthMixin): +class OAuth1ClientLoginHandlerLegacy(RequestHandler, OAuthMixin): def initialize(self, test, version): self._OAUTH_VERSION = version self._OAUTH_REQUEST_TOKEN_URL = test.get_url('/oauth1/server/request_token') @@ -59,8 +81,10 @@ class OAuth1ClientLoginHandler(RequestHandler, OAuthMixin): @asynchronous def get(self): if self.get_argument('oauth_token', None): - self.get_authenticated_user( - self.on_user, http_client=self.settings['http_client']) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + self.get_authenticated_user( + self.on_user, http_client=self.settings['http_client']) return res = self.authorize_redirect(http_client=self.settings['http_client']) assert isinstance(res, Future) @@ -78,6 +102,35 @@ class OAuth1ClientLoginHandler(RequestHandler, OAuthMixin): callback(dict(email='foo@example.com')) +class OAuth1ClientLoginHandler(RequestHandler, OAuthMixin): + def initialize(self, test, version): + self._OAUTH_VERSION = version + self._OAUTH_REQUEST_TOKEN_URL = test.get_url('/oauth1/server/request_token') + self._OAUTH_AUTHORIZE_URL = test.get_url('/oauth1/server/authorize') + self._OAUTH_ACCESS_TOKEN_URL = test.get_url('/oauth1/server/access_token') + + def _oauth_consumer_token(self): + return dict(key='asdf', secret='qwer') + + @gen.coroutine + def get(self): + if self.get_argument('oauth_token', None): + user = yield self.get_authenticated_user(http_client=self.settings['http_client']) + if user is None: + raise Exception("user is None") + self.finish(user) + return + yield self.authorize_redirect(http_client=self.settings['http_client']) + + @gen.coroutine + def _oauth_get_user_future(self, access_token): + if self.get_argument('fail_in_get_user', None): + raise Exception("failing in get_user") + if access_token != dict(key='uiop', secret='5678'): + raise Exception("incorrect access token %r" % access_token) + return dict(email='foo@example.com') + + class OAuth1ClientLoginCoroutineHandler(OAuth1ClientLoginHandler): """Replaces OAuth1ClientLoginCoroutineHandler's get() with a coroutine.""" @gen.coroutine @@ -172,7 +225,7 @@ class TwitterClientHandler(RequestHandler, TwitterMixin): return self.settings['http_client'] -class TwitterClientLoginHandler(TwitterClientHandler): +class TwitterClientLoginHandlerLegacy(TwitterClientHandler): @asynchronous def get(self): if self.get_argument("oauth_token", None): @@ -186,6 +239,18 @@ class TwitterClientLoginHandler(TwitterClientHandler): self.finish(user) +class TwitterClientLoginHandler(TwitterClientHandler): + @gen.coroutine + def get(self): + if self.get_argument("oauth_token", None): + user = yield self.get_authenticated_user() + if user is None: + raise Exception("user is None") + self.finish(user) + return + yield self.authorize_redirect() + + class TwitterClientLoginGenEngineHandler(TwitterClientHandler): @asynchronous @gen.engine @@ -211,15 +276,17 @@ class TwitterClientLoginGenCoroutineHandler(TwitterClientHandler): yield self.authorize_redirect() -class TwitterClientShowUserHandler(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. - response = yield gen.Task(self.twitter_request, - '/users/show/%s' % self.get_argument('name'), - access_token=dict(key='hjkl', secret='vbnm')) + 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') @@ -227,6 +294,22 @@ class TwitterClientShowUserHandler(TwitterClientHandler): self.finish(response) +class TwitterClientShowUserHandler(TwitterClientHandler): + @gen.coroutine + def get(self): + # TODO: would be nice to go through the login flow instead of + # cheating with a hard-coded access token. + try: + response = yield self.twitter_request( + '/users/show/%s' % self.get_argument('name'), + access_token=dict(key='hjkl', secret='vbnm')) + except AuthError: + self.set_status(500) + self.finish('error from twitter request') + else: + self.finish(response) + + class TwitterClientShowUserFutureHandler(TwitterClientHandler): @asynchronous @gen.engine @@ -279,12 +362,17 @@ class AuthTest(AsyncHTTPTestCase): return Application( [ # test endpoints + ('/legacy/openid/client/login', OpenIdClientLoginHandlerLegacy, dict(test=self)), ('/openid/client/login', OpenIdClientLoginHandler, dict(test=self)), + ('/legacy/oauth10/client/login', OAuth1ClientLoginHandlerLegacy, + dict(test=self, version='1.0')), ('/oauth10/client/login', OAuth1ClientLoginHandler, dict(test=self, version='1.0')), ('/oauth10/client/request_params', OAuth1ClientRequestParametersHandler, dict(version='1.0')), + ('/legacy/oauth10a/client/login', OAuth1ClientLoginHandlerLegacy, + dict(test=self, version='1.0a')), ('/oauth10a/client/login', OAuth1ClientLoginHandler, dict(test=self, version='1.0a')), ('/oauth10a/client/login_coroutine', @@ -297,11 +385,14 @@ class AuthTest(AsyncHTTPTestCase): ('/facebook/client/login', FacebookClientLoginHandler, dict(test=self)), + ('/legacy/twitter/client/login', TwitterClientLoginHandlerLegacy, dict(test=self)), ('/twitter/client/login', TwitterClientLoginHandler, dict(test=self)), ('/twitter/client/login_gen_engine', TwitterClientLoginGenEngineHandler, dict(test=self)), ('/twitter/client/login_gen_coroutine', TwitterClientLoginGenCoroutineHandler, dict(test=self)), + ('/legacy/twitter/client/show_user', + TwitterClientShowUserHandlerLegacy, dict(test=self)), ('/twitter/client/show_user', TwitterClientShowUserHandler, dict(test=self)), ('/twitter/client/show_user_future', @@ -325,6 +416,21 @@ class AuthTest(AsyncHTTPTestCase): facebook_api_key='test_facebook_api_key', facebook_secret='test_facebook_secret') + def test_openid_redirect_legacy(self): + response = self.fetch('/legacy/openid/client/login', follow_redirects=False) + self.assertEqual(response.code, 302) + self.assertTrue( + '/openid/server/authenticate?' in response.headers['Location']) + + def test_openid_get_user_legacy(self): + response = self.fetch('/legacy/openid/client/login?openid.mode=blah' + '&openid.ns.ax=http://openid.net/srv/ax/1.0' + '&openid.ax.type.email=http://axschema.org/contact/email' + '&openid.ax.value.email=foo@example.com') + response.rethrow() + parsed = json_decode(response.body) + self.assertEqual(parsed["email"], "foo@example.com") + def test_openid_redirect(self): response = self.fetch('/openid/client/login', follow_redirects=False) self.assertEqual(response.code, 302) @@ -340,6 +446,16 @@ class AuthTest(AsyncHTTPTestCase): parsed = json_decode(response.body) self.assertEqual(parsed["email"], "foo@example.com") + def test_oauth10_redirect_legacy(self): + response = self.fetch('/legacy/oauth10/client/login', follow_redirects=False) + self.assertEqual(response.code, 302) + self.assertTrue(response.headers['Location'].endswith( + '/oauth1/server/authorize?oauth_token=zxcv')) + # the cookie is base64('zxcv')|base64('1234') + self.assertTrue( + '_oauth_request_token="enhjdg==|MTIzNA=="' in response.headers['Set-Cookie'], + response.headers['Set-Cookie']) + def test_oauth10_redirect(self): response = self.fetch('/oauth10/client/login', follow_redirects=False) self.assertEqual(response.code, 302) @@ -350,6 +466,16 @@ class AuthTest(AsyncHTTPTestCase): '_oauth_request_token="enhjdg==|MTIzNA=="' in response.headers['Set-Cookie'], response.headers['Set-Cookie']) + def test_oauth10_get_user_legacy(self): + with ignore_deprecation(): + response = self.fetch( + '/legacy/oauth10/client/login?oauth_token=zxcv', + headers={'Cookie': '_oauth_request_token=enhjdg==|MTIzNA=='}) + response.rethrow() + parsed = json_decode(response.body) + self.assertEqual(parsed['email'], 'foo@example.com') + self.assertEqual(parsed['access_token'], dict(key='uiop', secret='5678')) + def test_oauth10_get_user(self): response = self.fetch( '/oauth10/client/login?oauth_token=zxcv', @@ -368,6 +494,26 @@ class AuthTest(AsyncHTTPTestCase): self.assertTrue('oauth_nonce' in parsed) self.assertTrue('oauth_signature' in parsed) + def test_oauth10a_redirect_legacy(self): + response = self.fetch('/legacy/oauth10a/client/login', follow_redirects=False) + self.assertEqual(response.code, 302) + self.assertTrue(response.headers['Location'].endswith( + '/oauth1/server/authorize?oauth_token=zxcv')) + # the cookie is base64('zxcv')|base64('1234') + self.assertTrue( + '_oauth_request_token="enhjdg==|MTIzNA=="' in response.headers['Set-Cookie'], + response.headers['Set-Cookie']) + + def test_oauth10a_get_user_legacy(self): + with ignore_deprecation(): + response = self.fetch( + '/legacy/oauth10a/client/login?oauth_token=zxcv', + headers={'Cookie': '_oauth_request_token=enhjdg==|MTIzNA=='}) + response.rethrow() + parsed = json_decode(response.body) + self.assertEqual(parsed['email'], 'foo@example.com') + self.assertEqual(parsed['access_token'], dict(key='uiop', secret='5678')) + def test_oauth10a_redirect(self): response = self.fetch('/oauth10a/client/login', follow_redirects=False) self.assertEqual(response.code, 302) @@ -428,6 +574,9 @@ class AuthTest(AsyncHTTPTestCase): '_oauth_request_token="enhjdg==|MTIzNA=="' in response.headers['Set-Cookie'], response.headers['Set-Cookie']) + def test_twitter_redirect_legacy(self): + self.base_twitter_redirect('/legacy/twitter/client/login') + def test_twitter_redirect(self): self.base_twitter_redirect('/twitter/client/login') @@ -451,6 +600,18 @@ class AuthTest(AsyncHTTPTestCase): u'screen_name': u'foo', u'username': u'foo'}) + def test_twitter_show_user_legacy(self): + response = self.fetch('/legacy/twitter/client/show_user?name=somebody') + response.rethrow() + self.assertEqual(json_decode(response.body), + {'name': 'Somebody', 'screen_name': 'somebody'}) + + def test_twitter_show_user_error_legacy(self): + with ExpectLog(gen_log, 'Error response HTTP 500'): + response = self.fetch('/legacy/twitter/client/show_user?name=error') + self.assertEqual(response.code, 500) + self.assertEqual(response.body, b'error from twitter request') + def test_twitter_show_user(self): response = self.fetch('/twitter/client/show_user?name=somebody') response.rethrow() @@ -458,8 +619,7 @@ class AuthTest(AsyncHTTPTestCase): {'name': 'Somebody', 'screen_name': 'somebody'}) def test_twitter_show_user_error(self): - with ExpectLog(gen_log, 'Error response HTTP 500'): - response = self.fetch('/twitter/client/show_user?name=error') + response = self.fetch('/twitter/client/show_user?name=error') self.assertEqual(response.code, 500) self.assertEqual(response.body, b'error from twitter request') diff --git a/tornado/test/concurrent_test.py b/tornado/test/concurrent_test.py index b00b80ddf69e325bacac46503ddacbec5fe63385..dca66547e3e9efdb6e8614bde104f4c9a7d2dca0 100644 --- a/tornado/test/concurrent_test.py +++ b/tornado/test/concurrent_test.py @@ -31,7 +31,7 @@ from tornado.log import app_log from tornado import stack_context from tornado.tcpserver import TCPServer from tornado.testing import AsyncTestCase, ExpectLog, bind_unused_port, gen_test -from tornado.test.util import unittest, skipBefore35, exec_test +from tornado.test.util import unittest, skipBefore35, exec_test, ignore_deprecation try: @@ -99,7 +99,8 @@ class ReturnFutureTest(AsyncTestCase): self.return_value(callback=self.stop) def test_callback_kw(self): - future = self.sync_future(callback=self.stop) + with ignore_deprecation(): + future = self.sync_future(callback=self.stop) result = self.wait() self.assertEqual(result, 42) self.assertEqual(future.result(), 42) @@ -107,7 +108,8 @@ class ReturnFutureTest(AsyncTestCase): def test_callback_positional(self): # When the callback is passed in positionally, future_wrap shouldn't # add another callback in the kwargs. - future = self.sync_future(self.stop) + with ignore_deprecation(): + future = self.sync_future(self.stop) result = self.wait() self.assertEqual(result, 42) self.assertEqual(future.result(), 42) @@ -154,20 +156,23 @@ class ReturnFutureTest(AsyncTestCase): self.assertEqual(future.result(), 42) def test_error_in_callback(self): - self.sync_future(callback=lambda future: 1 / 0) + with ignore_deprecation(): + self.sync_future(callback=lambda future: 1 / 0) # The exception gets caught by our StackContext and will be re-raised # when we wait. self.assertRaises(ZeroDivisionError, self.wait) def test_no_result_future(self): - future = self.no_result_future(self.stop) + with ignore_deprecation(): + future = self.no_result_future(self.stop) result = self.wait() self.assertIs(result, None) # result of this future is undefined, but not an error future.result() def test_no_result_future_callback(self): - future = self.no_result_future(callback=lambda: self.stop()) + with ignore_deprecation(): + future = self.no_result_future(callback=lambda: self.stop()) result = self.wait() self.assertIs(result, None) future.result() @@ -334,12 +339,14 @@ class ClientTestMixin(object): super(ClientTestMixin, self).tearDown() # type: ignore def test_callback(self): - self.client.capitalize("hello", callback=self.stop) + with ignore_deprecation(): + self.client.capitalize("hello", callback=self.stop) result = self.wait() self.assertEqual(result, "HELLO") def test_callback_error(self): - self.client.capitalize("HELLO", callback=self.stop) + with ignore_deprecation(): + self.client.capitalize("HELLO", callback=self.stop) self.assertRaisesRegexp(CapError, "already capitalized", self.wait) def test_future(self): diff --git a/tornado/test/util.py b/tornado/test/util.py index 63a9762f14affb81d80e41f4f7f60d079bbe6a34..7c9db84a661afce50bb9647b1aba07a129be5cfd 100644 --- a/tornado/test/util.py +++ b/tornado/test/util.py @@ -6,6 +6,7 @@ import platform import socket import sys import textwrap +import warnings from tornado.testing import bind_unused_port @@ -107,3 +108,11 @@ def subTest(test, *args, **kwargs): except AttributeError: subTest = contextlib.contextmanager(lambda *a, **kw: (yield)) return subTest(*args, **kwargs) + + +@contextlib.contextmanager +def ignore_deprecation(): + """Context manager to ignore deprecation warnings.""" + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + yield diff --git a/tornado/web.py b/tornado/web.py index a1d2aa5f01b14dd293709cb9c76f50433f2394ee..8a9a8053444fee5223a51694e30e28d14dc1f177 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -246,7 +246,7 @@ class RequestHandler(object): of the request method. Asynchronous support: Decorate this method with `.gen.coroutine` - or `.return_future` to make it asynchronous (the + or use ``async def`` to make it asynchronous (the `asynchronous` decorator cannot be used on `prepare`). If this method returns a `.Future` execution will not proceed until the `.Future` is done.