Proof-of-concept integration with PEP 492 async/await. · bdarnell/tornado@e3b71c3 (original) (raw)

4 files changed

lines changed

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
1 +from tornado.ioloop import IOLoop
2 +from tornado.httpclient import AsyncHTTPClient
3 +from tornado.options import parse_command_line, define, options
4 +from tornado.web import RequestHandler, Application
5 +
6 +define('port', default=8080)
7 +
8 +class FooHandler(RequestHandler):
9 +def get(self):
10 +self.write('hello')
11 +
12 +class BarHandler(RequestHandler):
13 +async def get(self):
14 +resp = await AsyncHTTPClient().fetch('http://localhost:%d/foo' %
15 +options.port)
16 +self.write('read %d bytes' % len(resp.body))
17 +
18 +if __name__ == '__main__':
19 +parse_command_line()
20 +app = Application([('/foo', FooHandler),
21 + ('/bar', BarHandler)], debug=True)
22 +app.listen(options.port)
23 +IOLoop.current().start()
Original file line number Diff line number Diff line change
@@ -310,6 +310,9 @@ def __del__(self):
310 310 app_log.error('Future %r exception was never retrieved: %s',
311 311 self, ''.join(tb).rstrip())
312 312
313 +def __await__(self):
314 +return (yield self)
315 +
313 316 TracebackFuture = Future
314 317
315 318 if futures is None:
Original file line number Diff line number Diff line change
@@ -202,6 +202,8 @@ def _make_coroutine_wrapper(func, replace_callback):
202 202 argument, so we cannot simply implement ``@engine`` in terms of
203 203 ``@coroutine``.
204 204 """
205 +import types
206 +func = types.coroutine(func)
205 207 @functools.wraps(func)
206 208 def wrapper(*args, **kwargs):
207 209 future = TracebackFuture()
@@ -1040,3 +1042,26 @@ def _(asyncio_future):
1040 1042
1041 1043 if singledispatch is not None:
1042 1044 convert_yielded = singledispatch(convert_yielded)
1045 +
1046 +import abc, types
1047 +class _Awaitable(abc.ABC):
1048 +@classmethod
1049 +def __subclasshook__(cls, C):
1050 +if cls is _Awaitable:
1051 +if C is Future:
1052 +# Futures are awaitable but we want to handle them natively
1053 +# instead of going through this wrapper.
1054 +return False
1055 +if any("__await__" in B.__dict__ for B in C.__mro__):
1056 +return True
1057 +return NotImplemented
1058 +# TODO: this claims all generators; we really only want it to apply when
1059 +# inspect.iscoroutine is true but that uses a flag on the instance.
1060 +_Awaitable.register(types.GeneratorType)
1061 +
1062 +@convert_yielded.register(_Awaitable)
1063 +def _(c):
1064 +@coroutine
1065 +def wrapped():
1066 +return (yield from c)
1067 +return wrapped()
Original file line number Diff line number Diff line change
@@ -1388,10 +1388,8 @@ def _execute(self, transforms, *args, **kwargs):
1388 1388 self.check_xsrf_cookie()
1389 1389
1390 1390 result = self.prepare()
1391 -if is_future(result):
1392 -result = yield result
1393 1391 if result is not None:
1394 -raise TypeError("Expected None, got %r" % result)
1392 +result = yield result
1395 1393 if self._prepared_future is not None:
1396 1394 # Tell the Application we've finished with prepare()
1397 1395 # and are ready for the body to arrive.
@@ -1411,10 +1409,8 @@ def _execute(self, transforms, *args, **kwargs):
1411 1409
1412 1410 method = getattr(self, self.request.method.lower())
1413 1411 result = method(*self.path_args, **self.path_kwargs)
1414 -if is_future(result):
1415 -result = yield result
1416 1412 if result is not None:
1417 -raise TypeError("Expected None, got %r" % result)
1413 +result = yield result
1418 1414 if self._auto_finish and not self._finished:
1419 1415 self.finish()
1420 1416 except Exception as e: