diff options
-rw-r--r-- | CHANGES.rst | 3 | ||||
-rw-r--r-- | docs/api.rst | 6 | ||||
-rw-r--r-- | src/jinja2/environment.py | 36 | ||||
-rw-r--r-- | tests/test_async.py | 15 |
4 files changed, 45 insertions, 15 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index a371b22..fee54a1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,6 +13,9 @@ Unreleased extensions shows more relevant context. :issue:`1429` - Fixed calling deprecated ``jinja2.Markup`` without an argument. Use ``markupsafe.Markup`` instead. :issue:`1438` +- Calling sync ``render`` for an async template uses ``asyncio.run`` + on Python >= 3.7. This fixes a deprecation that Python 3.10 + introduces. :issue:`1443` Version 3.0.0 diff --git a/docs/api.rst b/docs/api.rst index 127e2b8..9c6f3a1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -513,12 +513,12 @@ 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. + current thread. :func:`asyncio.get_running_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 + all calls and access, which adds overhead compared to purely async code. - Sync methods and filters become wrappers around their corresponding async implementations where needed. For example, ``render`` invokes diff --git a/src/jinja2/environment.py b/src/jinja2/environment.py index 9c173b2..8a831db 100644 --- a/src/jinja2/environment.py +++ b/src/jinja2/environment.py @@ -2,6 +2,7 @@ options. """ import os +import sys import typing import typing as t import weakref @@ -1278,8 +1279,22 @@ class Template: if self.environment.is_async: import asyncio - loop = asyncio.get_event_loop() - return loop.run_until_complete(self.render_async(*args, **kwargs)) + close = False + + if sys.version_info < (3, 7): + loop = asyncio.get_event_loop() + else: + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + close = True + + try: + return loop.run_until_complete(self.render_async(*args, **kwargs)) + finally: + if close: + loop.close() ctx = self.new_context(dict(*args, **kwargs)) @@ -1326,14 +1341,17 @@ class Template: if self.environment.is_async: import asyncio - loop = asyncio.get_event_loop() - async_gen = self.generate_async(*args, **kwargs) + async def to_list() -> t.List[str]: + return [x async for x in self.generate_async(*args, **kwargs)] - try: - while True: - yield loop.run_until_complete(async_gen.__anext__()) - except StopAsyncIteration: - return + if sys.version_info < (3, 7): + loop = asyncio.get_event_loop() + out = loop.run_until_complete(to_list()) + else: + out = asyncio.run(to_list()) + + yield from out + return ctx = self.new_context(dict(*args, **kwargs)) diff --git a/tests/test_async.py b/tests/test_async.py index f8be8df..e1c8d23 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -1,4 +1,5 @@ import asyncio +import sys import pytest @@ -13,9 +14,17 @@ from jinja2.exceptions import UndefinedError from jinja2.nativetypes import NativeEnvironment -def run(coro): - loop = asyncio.get_event_loop() - return loop.run_until_complete(coro) +if sys.version_info < (3, 7): + + def run(coro): + loop = asyncio.get_event_loop() + return loop.run_until_complete(coro) + + +else: + + def run(coro): + return asyncio.run(coro) def test_basic_async(): |