Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add {%asynchronous%} directive to allow templates to call asynchronous functions with gen.Task and return their results through a callback #553

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions tornado/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ def add(x, y):
set via ``{% set %}``, or the use of ``{% break %}`` or ``{% continue %}``
within loops.

``{% asynchronous *boolean* %}``
Sets whether the template returns its result asynchronously. If ``False``
(the default), `Template.generate` will return synchronously. If ``True``,
the `callback` parameter passed to `Template.generate` will be called with
the result, the `Task` class from the `gen` module will be available, and
the template generation function will be wrapped with `gen.engine`.::

{% asynchronous True %}
{{ yield Task(foo, 'bar') }}

``{% autoescape *function* %}``
Sets the autoescape mode for the current file. This does not affect
other files, even those referenced by ``{% include %}``. Note that
Expand Down Expand Up @@ -190,7 +200,7 @@ def add(x, y):
import re
import threading

from tornado import escape
from tornado import escape, gen
from tornado.util import bytes_type, ObjectDict

_DEFAULT_AUTOESCAPE = "xhtml_escape"
Expand All @@ -216,6 +226,7 @@ def __init__(self, template_string, name="<string>", loader=None,
else:
self.autoescape = _DEFAULT_AUTOESCAPE
self.namespace = loader.namespace if loader else {}
self.asynchronous = False
reader = _TemplateReader(name, escape.native_str(template_string))
self.file = _File(self, _parse(reader, self))
self.code = self._generate_python(loader, compress_whitespace)
Expand Down Expand Up @@ -249,6 +260,8 @@ def generate(self, **kwargs):
"__name__": self.name.replace('.', '_'),
"__loader__": ObjectDict(get_source=lambda name: self.code),
}
if self.asynchronous:
namespace["Task"] = gen.Task
namespace.update(self.namespace)
namespace.update(kwargs)
exec self.compiled in namespace
Expand All @@ -257,6 +270,9 @@ def generate(self, **kwargs):
# we've generated a new template (mainly for this module's
# unittests, where different tests reuse the same name).
linecache.clearcache()
assert kwargs.get("callback") or not self.asynchronous
if self.asynchronous:
execute = gen.engine(execute)
try:
return execute()
except Exception:
Expand Down Expand Up @@ -407,7 +423,10 @@ def generate(self, writer):
writer.write_line("_buffer = []", self.line)
writer.write_line("_append = _buffer.append", self.line)
self.body.generate(writer)
writer.write_line("return _utf8('').join(_buffer)", self.line)
if writer.current_template.asynchronous:
writer.write_line("callback(_utf8('').join(_buffer))", self.line)
else:
writer.write_line("return _utf8('').join(_buffer)", self.line)

def each_child(self):
return (self.body,)
Expand Down Expand Up @@ -786,7 +805,8 @@ def _parse(reader, template, in_block=None, in_loop=None):
return body

elif operator in ("extends", "include", "set", "import", "from",
"comment", "autoescape", "raw", "module"):
"comment", "autoescape", "asynchronous", "raw",
"module"):
if operator == "comment":
continue
if operator == "extends":
Expand All @@ -813,6 +833,9 @@ def _parse(reader, template, in_block=None, in_loop=None):
fn = None
template.autoescape = fn
continue
elif operator == "asynchronous":
template.asynchronous = suffix.strip() == "True"
continue
elif operator == "raw":
block = _Expression(suffix, line, raw=True)
elif operator == "module":
Expand Down
10 changes: 10 additions & 0 deletions tornado/test/template_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,16 @@ def test_break_in_apply(self):
except ParseError:
pass

def test_asynchronous(self):
def foo(arg, callback):
callback("foo-"+arg)
results = []
def bar(result):
results.append(result)
Template("{%asynchronous True%}{%set result = yield Task(foo, 'a')%}{{result}}").generate(foo=foo, callback=bar)
Template("{%asynchronous True%}{{yield Task(foo, 'b')}}").generate(foo=foo, callback=bar)
self.assertEqual(results, ["foo-a", "foo-b"])


class StackTraceTest(LogTrapTestCase):
def test_error_line_number_expression(self):
Expand Down