Merge branch '3.0.x'

This commit is contained in:
David Lord 2021-05-18 13:40:36 -07:00
commit cdbda09e44
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
8 changed files with 79 additions and 62 deletions

View File

@ -6,11 +6,17 @@ Version 3.1.0
Unreleased
Version 3.0.1
Version 3.0.2
-------------
Unreleased
Version 3.0.1
-------------
Released 2021-05-18
- Update MarkupSafe dependency to >= 2.0. :pr:`1418`
- Mark top-level names as exported so type checking understands
imports in user projects. :issue:`1426`
@ -19,6 +25,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

View File

@ -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

View File

@ -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))

View File

@ -1,4 +1,4 @@
import os
from pathlib import Path
import pytest
@ -27,8 +27,8 @@ def package_loader():
@pytest.fixture
def filesystem_loader():
"""returns FileSystemLoader initialized to res/templates directory"""
here = os.path.dirname(os.path.abspath(__file__))
return loaders.FileSystemLoader(here + "/res/templates")
here = Path(__file__).parent.resolve()
return loaders.FileSystemLoader(here / "res" / "templates")
@pytest.fixture

View File

@ -1,6 +1,6 @@
import os
import shutil
import tempfile
from pathlib import Path
import pytest
@ -242,13 +242,12 @@ class TestStreaming:
assert not stream.buffered
def test_dump_stream(self, env):
tmp = tempfile.mkdtemp()
tmp = Path(tempfile.mkdtemp())
try:
tmpl = env.from_string("\u2713")
stream = tmpl.stream()
stream.dump(os.path.join(tmp, "dump.txt"), "utf-8")
with open(os.path.join(tmp, "dump.txt"), "rb") as f:
assert f.read() == b"\xe2\x9c\x93"
stream.dump(str(tmp / "dump.txt"), "utf-8")
assert (tmp / "dump.txt").read_bytes() == b"\xe2\x9c\x93"
finally:
shutil.rmtree(tmp)

View File

@ -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():

View File

@ -8,6 +8,7 @@ import sys
import tempfile
import time
import weakref
from pathlib import Path
import pytest
@ -32,8 +33,7 @@ class TestLoaders:
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
def test_filesystem_loader_overlapping_names(self, filesystem_loader):
res = os.path.dirname(filesystem_loader.searchpath[0])
t2_dir = os.path.join(res, "templates2")
t2_dir = Path(filesystem_loader.searchpath[0]) / ".." / "templates2"
# Make "foo" show up before "foo/test.html".
filesystem_loader.searchpath.insert(0, t2_dir)
e = Environment(loader=filesystem_loader)
@ -118,9 +118,7 @@ class TestLoaders:
class TestFileSystemLoader:
searchpath = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "res", "templates"
)
searchpath = (Path(__file__) / ".." / "res" / "templates").resolve()
@staticmethod
def _test_common(env):
@ -131,24 +129,20 @@ class TestFileSystemLoader:
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
def test_searchpath_as_str(self):
filesystem_loader = loaders.FileSystemLoader(self.searchpath)
filesystem_loader = loaders.FileSystemLoader(str(self.searchpath))
env = Environment(loader=filesystem_loader)
self._test_common(env)
def test_searchpath_as_pathlib(self):
import pathlib
searchpath = pathlib.Path(self.searchpath)
filesystem_loader = loaders.FileSystemLoader(searchpath)
filesystem_loader = loaders.FileSystemLoader(self.searchpath)
env = Environment(loader=filesystem_loader)
self._test_common(env)
def test_searchpath_as_list_including_pathlib(self):
import pathlib
searchpath = pathlib.Path(self.searchpath)
filesystem_loader = loaders.FileSystemLoader(["/tmp/templates", searchpath])
filesystem_loader = loaders.FileSystemLoader(
["/tmp/templates", self.searchpath]
)
env = Environment(loader=filesystem_loader)
self._test_common(env)
@ -160,7 +154,7 @@ class TestFileSystemLoader:
tmpl2 = env.get_template("test.html")
assert tmpl1 is tmpl2
os.utime(os.path.join(self.searchpath, "test.html"), (time.time(), time.time()))
os.utime(self.searchpath / "test.html", (time.time(), time.time()))
tmpl3 = env.get_template("test.html")
assert tmpl1 is not tmpl3
@ -282,10 +276,7 @@ class TestModuleLoader:
self.compile_down(prefix_loader)
mod_path = self.mod_env.loader.module.__path__[0]
import pathlib
mod_loader = loaders.ModuleLoader(pathlib.Path(mod_path))
mod_loader = loaders.ModuleLoader(Path(mod_path))
self.mod_env = Environment(loader=mod_loader)
self._test_common()
@ -294,10 +285,7 @@ class TestModuleLoader:
self.compile_down(prefix_loader)
mod_path = self.mod_env.loader.module.__path__[0]
import pathlib
mod_loader = loaders.ModuleLoader([pathlib.Path(mod_path), "/tmp/templates"])
mod_loader = loaders.ModuleLoader([Path(mod_path), "/tmp/templates"])
self.mod_env = Environment(loader=mod_loader)
self._test_common()
@ -305,7 +293,7 @@ class TestModuleLoader:
@pytest.fixture()
def package_dir_loader(monkeypatch):
monkeypatch.syspath_prepend(os.path.dirname(__file__))
monkeypatch.syspath_prepend(Path(__file__).parent)
return PackageLoader("res")
@ -327,9 +315,8 @@ def test_package_dir_list(package_dir_loader):
@pytest.fixture()
def package_zip_loader(monkeypatch):
monkeypatch.syspath_prepend(
os.path.join(os.path.dirname(__file__), "res", "package.zip")
)
package_zip = (Path(__file__) / ".." / "res" / "package.zip").resolve()
monkeypatch.syspath_prepend(package_zip)
return PackageLoader("t_pack")

View File

@ -23,22 +23,17 @@ class TestLRUCache:
d["c"] = 3
d["a"]
d["d"] = 4
assert len(d) == 3
assert "a" in d and "c" in d and "d" in d and "b" not in d
assert d.keys() == ["d", "a", "c"]
def test_itervalues(self):
def test_values(self):
cache = LRUCache(3)
cache["b"] = 1
cache["a"] = 2
values = [v for v in cache.values()]
assert len(values) == 2
assert 1 in values
assert 2 in values
assert cache.values() == [2, 1]
def test_itervalues_empty(self):
def test_values_empty(self):
cache = LRUCache(2)
values = [v for v in cache.values()]
assert len(values) == 0
assert cache.values() == []
def test_pickleable(self):
cache = LRUCache(2)
@ -61,7 +56,7 @@ class TestLRUCache:
assert copy._queue == cache._queue
copy["c"] = 3
assert copy._queue != cache._queue
assert "a" not in copy and "b" in copy and "c" in copy
assert copy.keys() == ["c", "b"]
def test_clear(self):
d = LRUCache(3)