未验证 提交 f98d06dd 编写于 作者: B Ben Darnell 提交者: GitHub

Merge pull request #2317 from bdarnell/auth-deprecation

auth: Add deprecation warnings for callback-based interfaces
......@@ -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"
......
......@@ -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:
......
......@@ -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')
......
......@@ -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):
......
......@@ -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
......@@ -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.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册