Merge branch '2.11.x'

This commit is contained in:
David Lord 2020-02-27 11:16:01 -08:00
commit 45a76a3794
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
4 changed files with 37 additions and 28 deletions

View File

@ -19,7 +19,8 @@ Unreleased
- Fix a bug that caused callable objects with ``__getattr__``, like
:class:`~unittest.mock.Mock` to be treated as a
:func:`contextfunction`. :issue:`1145`
- Update ``wordcount`` filter to trigger :class:`Undefined` methods
by wrapping the input in :func:`soft_str`. :pr:`1160`
Version 2.11.1
--------------

View File

@ -477,37 +477,38 @@ Builtin bytecode caches:
Async Support
-------------
Starting with version 2.9, Jinja also supports the Python `async` and
`await` constructs. As far as template designers go this feature is
entirely opaque to them however as a developer you should be aware of how
it's implemented as it influences what type of APIs you can safely expose
to the template environment.
.. versionadded:: 2.9
First you need to be aware that by default async support is disabled as
enabling it will generate different template code behind the scenes which
passes everything through the asyncio event loop. This is important to
understand because it has some impact to what you are doing:
Jinja supports the Python ``async`` and ``await`` syntax. For the
template designer, this support (when enabled) is entirely transparent,
templates continue to look exactly the same. However, developers should
be aware of the implementation as it affects what types of APIs you can
use.
* template rendering will require an event loop to be set for the
current thread (``asyncio.get_event_loop`` needs to return one)
* all template generation code internally runs async generators which
means that you will pay a performance penalty even if the non sync
methods are used!
* The sync methods are based on async methods if the async mode is
enabled which means that `render` for instance will internally invoke
`render_async` and run it as part of the current event loop until the
execution finished.
By default, async support is disabled. Enabling it will cause the
environment to compile different code behind the scenes in order to
handle async and sync code in an asyncio event loop. This has the
following implications:
- Template rendering requires an event loop to be available to the
current thread. :func:`asyncio.get_event_loop` must return an event
loop.
- The compiled code uses ``await`` for functions and attributes, and
uses ``async for`` loops. In order to support using both async and
sync functions in this context, a small wrapper is placed around
all calls and access, which add overhead compared to purely async
code.
- Sync methods and filters become wrappers around their corresponding
async implementations where needed. For example, ``render`` invokes
``async_render``, and ``|map`` supports async iterables.
Awaitable objects can be returned from functions in templates and any
function call in a template will automatically await the result. This
means that you can provide a method that asynchronously loads data
from a database if you so desire and from the template designer's point of
view this is just another function they can call. This means that the
``await`` you would normally issue in Python is implied. However this
only applies to function calls. If an attribute for instance would be an
awaitable object then this would not result in the expected behavior.
function call in a template will automatically await the result. The
``await`` you would normally add in Python is implied. For example, you
can provide a method that asynchronously loads data from a database, and
from the template designer's point of view it can be called like any
other function.
Likewise iterations with a `for` loop support async iterators.
.. _policies:

View File

@ -741,7 +741,7 @@ def do_wordwrap(
def do_wordcount(s):
"""Count the words in that string."""
return len(_word_re.findall(s))
return len(_word_re.findall(soft_str(s)))
def do_int(value, default=0, base=10):

View File

@ -5,6 +5,8 @@ import pytest
from jinja2 import Environment
from jinja2 import Markup
from jinja2 import StrictUndefined
from jinja2 import UndefinedError
class Magic:
@ -363,6 +365,11 @@ class TestFilter:
tmpl = env.from_string('{{ "foo bar baz"|wordcount }}')
assert tmpl.render() == "3"
strict_env = Environment(undefined=StrictUndefined)
t = strict_env.from_string("{{ s|wordcount }}")
with pytest.raises(UndefinedError):
t.render()
def test_block(self, env):
tmpl = env.from_string("{% filter lower|escape %}<HEHE>{% endfilter %}")
assert tmpl.render() == "&lt;hehe&gt;"