Repository: pallets/jinja (python)

Issue:
Python 3.10: tests fail due to `DeprecationWarning: There is no current event loop`
The test suite is failing against Python 3.10.0b1 due to a lot of `DeprecationWarning`s, e.g.:

```pytb
==================================================================== FAILURES ====================================================================
________________________________________________________________ test_basic_async ________________________________________________________________
tests/test_async.py:29: in test_basic_async
    rv = run(func())
tests/test_async.py:17: in run
    loop = asyncio.get_event_loop()
E   DeprecationWarning: There is no current event loop
______________________________________________________________ test_await_on_calls _______________________________________________________________
tests/test_async.py:45: in test_await_on_calls
    rv = run(func())
tests/test_async.py:17: in run
    loop = asyncio.get_event_loop()
E   DeprecationWarning: There is no current event loop
_______________________________________________________ test_await_on_calls_normal_render ________________________________________________________
tests/test_async.py:58: in test_await_on_calls_normal_render
    rv = t.render(async_func=async_func, normal_func=normal_func)
.tox/py310/lib/python3.10/site-packages/jinja2/environment.py:1281: in render
    loop = asyncio.get_event_loop()
E   DeprecationWarning: There is no current event loop
[...]
```

To reproduce:
```
tox -e py310
```

Environment:

- Python version: 3.10.0b1
- Jinja version: ab7840328f3c6303b4509972f7751e9ab1ee36c7


Pull request:
use asyncio.run
Python 3.10 shows a deprecation warning when using `asyncio.get_event_loop` while not in an event loop, which is the case when running the sync `render` method for an async template. Use `asyncio.run` instead, which manages running in an event loop.

`generate` is no longer particularly useful when called on an async template, as calling `run` over and over again would create new loops and be inefficient. So it collects the entire render then yields it item by item so that it's still a generator. `generate` already doesn't seem particularly useful, as only very simple templates without blocks or other internal buffers will actually yield one statement at a time.

I think we should deprecate calling `render` and `generate` if the template is async. It's a blocking call in async mode, and it doesn't really make sense to render an async environment from sync code, as you'd have to manage everything manually for that to be useful. It also requires ignoring some type errors. Users should call `render_async` from a running loop when using an async environment.

<!--
Link to relevant issues or previous PRs, one per line. Use "fixes" to automatically close an issue.
-->

- fixes #1443 

<!--
Ensure each step in CONTRIBUTING.rst is complete by adding an "x" to each box below.

If only docs were changed, these aren't relevant and can be removed.
-->

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [x] Add or update relevant docs, in the docs folder and in code.
- [x] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [x] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index a371b226..fee54a16 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 127e2b85..9c6f3a12 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 9c173b25..8a831db8 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))
 


Test patch:
diff --git a/tests/test_async.py b/tests/test_async.py
index f8be8dff..e1c8d234 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():