Skip to content

Commit

Permalink
Use functools.singledispatch for convert_yielded when available.
Browse files Browse the repository at this point in the history
Register a converter for asyncio.Future and add tests.
  • Loading branch information
bdarnell committed Jan 19, 2015
1 parent f7167d0 commit d6a9409
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 5 deletions.
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions tornado/gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ def get(self):
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
Expand Down Expand Up @@ -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)
10 changes: 10 additions & 0 deletions tornado/platform/asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
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

Expand Down Expand Up @@ -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
68 changes: 68 additions & 0 deletions tornado/test/asyncio_test.py
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions tornado/test/runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
'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',
Expand Down
6 changes: 4 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit d6a9409

Please sign in to comment.