Register a converter for asyncio.Future and add tests.
install:
# always install unittest2 on py26 even if $DEPS is unset
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then travis_retry pip install unittest2; fi
- - if [[ $TRAVIS_PYTHON_VERSION == 2* && $DEPS == true ]]; then travis_retry pip install futures mock Monotime==1.0; fi
- - if [[ $TRAVIS_PYTHON_VERSION == 'pypy' && $DEPS == true ]]; then travis_retry pip install futures mock; fi
+ - if [[ $TRAVIS_PYTHON_VERSION == 2* && $DEPS == true ]]; then travis_retry pip install futures mock Monotime==1.0 singledispatch; fi
+ - if [[ $TRAVIS_PYTHON_VERSION == 'pypy' && $DEPS == true ]]; then travis_retry pip install futures mock singledispatch; fi
# TODO(bdarnell): pycares tests are currently disabled on travis due to ipv6 issues.
#- if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* && $DEPS == true ]]; then travis_retry pip install pycares; fi
- if [[ $TRAVIS_PYTHON_VERSION != 'pypy'* && $DEPS == true ]]; then travis_retry pip install pycurl; fi
- - if [[ $TRAVIS_PYTHON_VERSION == '3.2' && $DEPS == true ]]; then travis_retry pip install mock; fi
+ - if [[ $TRAVIS_PYTHON_VERSION == '3.2' && $DEPS == true ]]; then travis_retry pip install mock singledispatch; fi
# Twisted runs on 2.x and 3.3+, but is flaky on pypy.
- if [[ $TRAVIS_PYTHON_VERSION != '3.2' && $TRAVIS_PYTHON_VERSION != 'pypy'* && $DEPS == true ]]; then travis_retry travis_retry pip install Twisted; fi
- if [[ $TRAVIS_PYTHON_VERSION == '3.4' && $DEPS == true ]]; then travis_retry travis_retry pip install sphinx==1.2.2 sphinx_rtd_theme; fi
from tornado.log import app_log
from tornado import stack_context
+try:
+ from functools import singledispatch # py34+
+except ImportError as e:
+ try:
+ from singledispatch import singledispatch # backport
+ except ImportError:
+ singledispatch = None
+
class KeyReuseError(Exception):
pass
return yielded
else:
raise BadYieldError("yielded unknown object %r" % (yielded,))
+
+if singledispatch is not None:
+ convert_yielded = singledispatch(convert_yielded)
from __future__ import absolute_import, division, print_function, with_statement
import functools
+import tornado.concurrent
+from tornado.gen import convert_yielded
from tornado.ioloop import IOLoop
from tornado import stack_context
def initialize(self):
super(AsyncIOLoop, self).initialize(asyncio.new_event_loop(),
close_loop=True)
+
+
+if hasattr(convert_yielded, 'register'):
+ @convert_yielded.register(asyncio.Future)
+ def _(af):
+ tf = tornado.concurrent.Future()
+ tornado.concurrent.chain_future(af, tf)
+ return tf
--- /dev/null
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from __future__ import absolute_import, division, print_function, with_statement
+
+import sys
+import textwrap
+
+from tornado import gen
+from tornado.testing import AsyncTestCase, gen_test
+from tornado.test.util import unittest
+
+try:
+ from tornado.platform.asyncio import asyncio, AsyncIOLoop
+except ImportError:
+ asyncio = None
+
+skipIfNoSingleDispatch = unittest.skipIf(
+ gen.singledispatch is None, "singledispatch module not present")
+
+@unittest.skipIf(asyncio is None, "asyncio module not present")
+class AsyncIOLoopTest(AsyncTestCase):
+ def get_new_ioloop(self):
+ io_loop = AsyncIOLoop()
+ asyncio.set_event_loop(io_loop.asyncio_loop)
+ return io_loop
+
+ def test_asyncio_callback(self):
+ # Basic test that the asyncio loop is set up correctly.
+ asyncio.get_event_loop().call_soon(self.stop)
+ self.wait()
+
+ @skipIfNoSingleDispatch
+ @gen_test
+ def test_asyncio_future(self):
+ # Test that we can yield an asyncio future from a tornado coroutine.
+ # Without 'yield from', we must wrap coroutines in asyncio.async.
+ x = yield asyncio.async(
+ asyncio.get_event_loop().run_in_executor(None, lambda: 42))
+ self.assertEqual(x, 42)
+
+ @unittest.skipIf(sys.version_info < (3, 3),
+ 'PEP 380 not available')
+ @skipIfNoSingleDispatch
+ @gen_test
+ def test_asyncio_yield_from(self):
+ # Test that we can use asyncio coroutines with 'yield from'
+ # instead of asyncio.async(). This requires python 3.3 syntax.
+ global_namespace = dict(globals(), **locals())
+ local_namespace = {}
+ exec(textwrap.dedent("""
+ @gen.coroutine
+ def f():
+ event_loop = asyncio.get_event_loop()
+ x = yield from event_loop.run_in_executor(None, lambda: 42)
+ return x
+ """), global_namespace, local_namespace)
+ result = yield local_namespace['f']()
+ self.assertEqual(result, 42)
'tornado.httputil.doctests',
'tornado.iostream.doctests',
'tornado.util.doctests',
+ 'tornado.test.asyncio_test',
'tornado.test.auth_test',
'tornado.test.concurrent_test',
'tornado.test.curl_httpclient_test',
{py2,py3}-select,
{py2,py26,py3}-full-twisted,
py2-twistedlayered,
- {py3,py33}-asyncio,
- {py26,py2}-trollius,
+ {py3,py33}-full-asyncio,
+ {py26,py2}-full-trollius,
# Alternate Resolvers.
{py2,py3}-full-{threadedresolver},
{py2,py26,py27,pypy}-full: futures
# mock became standard in py33
{py2,py26,py27,pypy,py3,py32,pypy3}-full: mock
+ # singledispatch became standard in py34
+ {py2,py26,py27,pypy,py3,py32,py33}-full: singledispatch
py33-asyncio: asyncio
trollius: trollius
py2-monotonic: Monotime