]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Use functools.singledispatch for convert_yielded when available.
authorBen Darnell <ben@bendarnell.com>
Mon, 19 Jan 2015 04:54:41 +0000 (23:54 -0500)
committerBen Darnell <ben@bendarnell.com>
Mon, 19 Jan 2015 15:12:56 +0000 (10:12 -0500)
Register a converter for asyncio.Future and add tests.

.travis.yml
tornado/gen.py
tornado/platform/asyncio.py
tornado/test/asyncio_test.py [new file with mode: 0644]
tornado/test/runtests.py
tox.ini

index a47dceb9eaa136f899aa31011cd1c7b6f9acba09..e94f5cd326c79a338a181823b0a2b6fc8489b9f8 100644 (file)
@@ -16,12 +16,12 @@ env:
 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
index 7333bd6d7d4d3541fc8cdb9b340aa65433d103b5..f41cbafd984eedbb7510f0b13a46dd03cbf378a3 100644 (file)
@@ -60,6 +60,14 @@ from tornado.ioloop import IOLoop
 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
@@ -900,3 +908,6 @@ def convert_yielded(yielded):
         return yielded
     else:
         raise BadYieldError("yielded unknown object %r" % (yielded,))
+
+if singledispatch is not None:
+    convert_yielded = singledispatch(convert_yielded)
index dd6722a49d2f7c987878c7ce8484bf7a92cbcd77..5842d108c92e09d62f96b03fa704afd2b6b56676 100644 (file)
@@ -12,6 +12,8 @@ unfinished callbacks on the event loop that fail when it resumes)
 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
 
@@ -138,3 +140,11 @@ class AsyncIOLoop(BaseAsyncIOLoop):
     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
diff --git a/tornado/test/asyncio_test.py b/tornado/test/asyncio_test.py
new file mode 100644 (file)
index 0000000..cb99074
--- /dev/null
@@ -0,0 +1,68 @@
+# 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)
index ba08c1c7d2a1a86dccd97adbd6a53df8533014a5..8ab5f151a223104e39dd534c16c8c834d8b55f5b 100644 (file)
@@ -22,6 +22,7 @@ TEST_MODULES = [
     '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',
diff --git a/tox.ini b/tox.ini
index 51233eb23abad521b0463e245d64936fcb63f430..7aec7fafc7ff9f5dedea858c080768206ea78081 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -30,8 +30,8 @@ envlist =
         {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},
@@ -81,6 +81,8 @@ deps =
      {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