This commit is contained in:
Iliyan Angelov
2025-12-01 06:50:10 +02:00
parent 91f51bc6fe
commit 62c1fe5951
4682 changed files with 544807 additions and 31208 deletions

View File

@@ -1,6 +1,7 @@
"""Classes for managing templates and their runtime and compile time
options.
"""
import os
import typing
import typing as t
@@ -20,10 +21,10 @@ from .defaults import BLOCK_END_STRING
from .defaults import BLOCK_START_STRING
from .defaults import COMMENT_END_STRING
from .defaults import COMMENT_START_STRING
from .defaults import DEFAULT_FILTERS
from .defaults import DEFAULT_FILTERS # type: ignore[attr-defined]
from .defaults import DEFAULT_NAMESPACE
from .defaults import DEFAULT_POLICIES
from .defaults import DEFAULT_TESTS
from .defaults import DEFAULT_TESTS # type: ignore[attr-defined]
from .defaults import KEEP_TRAILING_NEWLINE
from .defaults import LINE_COMMENT_PREFIX
from .defaults import LINE_STATEMENT_PREFIX
@@ -55,6 +56,7 @@ from .utils import missing
if t.TYPE_CHECKING:
import typing_extensions as te
from .bccache import BytecodeCache
from .ext import Extension
from .loaders import BaseLoader
@@ -79,7 +81,7 @@ def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_b
def create_cache(
size: int,
) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]:
) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]:
"""Return the cache class for the given size."""
if size == 0:
return None
@@ -91,13 +93,13 @@ def create_cache(
def copy_cache(
cache: t.Optional[t.MutableMapping],
) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]:
cache: t.Optional[t.MutableMapping[t.Any, t.Any]],
) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]:
"""Create an empty copy of the given cache."""
if cache is None:
return None
if type(cache) is dict:
if type(cache) is dict: # noqa E721
return {}
return LRUCache(cache.capacity) # type: ignore
@@ -121,7 +123,7 @@ def load_extensions(
return result
def _environment_config_check(environment: "Environment") -> "Environment":
def _environment_config_check(environment: _env_bound) -> _env_bound:
"""Perform a sanity check on the environment."""
assert issubclass(
environment.undefined, Undefined
@@ -404,8 +406,8 @@ class Environment:
cache_size: int = missing,
auto_reload: bool = missing,
bytecode_cache: t.Optional["BytecodeCache"] = missing,
enable_async: bool = False,
) -> "Environment":
enable_async: bool = missing,
) -> "te.Self":
"""Create a new overlay environment that shares all the data with the
current environment except for cache and the overridden attributes.
Extensions cannot be removed for an overlayed environment. An overlayed
@@ -417,8 +419,11 @@ class Environment:
copied over so modifications on the original environment may not shine
through.
.. versionchanged:: 3.1.5
``enable_async`` is applied correctly.
.. versionchanged:: 3.1.2
Added the ``newline_sequence``,, ``keep_trailing_newline``,
Added the ``newline_sequence``, ``keep_trailing_newline``,
and ``enable_async`` parameters to match ``__init__``.
"""
args = dict(locals())
@@ -670,7 +675,7 @@ class Environment:
stream = ext.filter_stream(stream) # type: ignore
if not isinstance(stream, TokenStream):
stream = TokenStream(stream, name, filename) # type: ignore
stream = TokenStream(stream, name, filename)
return stream
@@ -701,18 +706,17 @@ class Environment:
.. versionadded:: 2.5
"""
return compile(source, filename, "exec") # type: ignore
return compile(source, filename, "exec")
@typing.overload
def compile( # type: ignore
def compile(
self,
source: t.Union[str, nodes.Template],
name: t.Optional[str] = None,
filename: t.Optional[str] = None,
raw: "te.Literal[False]" = False,
defer_init: bool = False,
) -> CodeType:
...
) -> CodeType: ...
@typing.overload
def compile(
@@ -722,8 +726,7 @@ class Environment:
filename: t.Optional[str] = None,
raw: "te.Literal[True]" = ...,
defer_init: bool = False,
) -> str:
...
) -> str: ...
@internalcode
def compile(
@@ -814,7 +817,7 @@ class Environment:
def compile_templates(
self,
target: t.Union[str, os.PathLike],
target: t.Union[str, "os.PathLike[str]"],
extensions: t.Optional[t.Collection[str]] = None,
filter_func: t.Optional[t.Callable[[str], bool]] = None,
zip: t.Optional[str] = "deflated",
@@ -858,7 +861,10 @@ class Environment:
f.write(data.encode("utf8"))
if zip is not None:
from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED
from zipfile import ZIP_DEFLATED
from zipfile import ZIP_STORED
from zipfile import ZipFile
from zipfile import ZipInfo
zip_file = ZipFile(
target, "w", dict(deflated=ZIP_DEFLATED, stored=ZIP_STORED)[zip]
@@ -920,7 +926,7 @@ class Environment:
)
def filter_func(x: str) -> bool:
return "." in x and x.rsplit(".", 1)[1] in extensions # type: ignore
return "." in x and x.rsplit(".", 1)[1] in extensions
if filter_func is not None:
names = [name for name in names if filter_func(name)]
@@ -1245,7 +1251,7 @@ class Template:
namespace: t.MutableMapping[str, t.Any],
globals: t.MutableMapping[str, t.Any],
) -> "Template":
t: "Template" = object.__new__(cls)
t: Template = object.__new__(cls)
t.environment = environment
t.globals = globals
t.name = namespace["name"]
@@ -1253,7 +1259,7 @@ class Template:
t.blocks = namespace["blocks"]
# render function and module
t.root_render_func = namespace["root"] # type: ignore
t.root_render_func = namespace["root"]
t._module = None
# debug and loader helpers
@@ -1279,19 +1285,7 @@ class Template:
if self.environment.is_async:
import asyncio
close = False
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()
return asyncio.run(self.render_async(*args, **kwargs))
ctx = self.new_context(dict(*args, **kwargs))
@@ -1349,13 +1343,13 @@ class Template:
ctx = self.new_context(dict(*args, **kwargs))
try:
yield from self.root_render_func(ctx) # type: ignore
yield from self.root_render_func(ctx)
except Exception:
yield self.environment.handle_exception()
async def generate_async(
self, *args: t.Any, **kwargs: t.Any
) -> t.AsyncIterator[str]:
) -> t.AsyncGenerator[str, object]:
"""An async version of :meth:`generate`. Works very similarly but
returns an async iterator instead.
"""
@@ -1367,8 +1361,14 @@ class Template:
ctx = self.new_context(dict(*args, **kwargs))
try:
async for event in self.root_render_func(ctx): # type: ignore
yield event
agen = self.root_render_func(ctx)
try:
async for event in agen: # type: ignore
yield event
finally:
# we can't use async with aclosing(...) because that's only
# in 3.10+
await agen.aclose() # type: ignore
except Exception:
yield self.environment.handle_exception()
@@ -1417,7 +1417,9 @@ class Template:
"""
ctx = self.new_context(vars, shared, locals)
return TemplateModule(
self, ctx, [x async for x in self.root_render_func(ctx)] # type: ignore
self,
ctx,
[x async for x in self.root_render_func(ctx)], # type: ignore
)
@internalcode
@@ -1532,7 +1534,7 @@ class TemplateModule:
" API you are using."
)
body_stream = list(template.root_render_func(context)) # type: ignore
body_stream = list(template.root_render_func(context))
self._body_stream = body_stream
self.__dict__.update(context.get_exported())
@@ -1564,7 +1566,7 @@ class TemplateExpression:
def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Optional[t.Any]:
context = self._template.new_context(dict(*args, **kwargs))
consume(self._template.root_render_func(context)) # type: ignore
consume(self._template.root_render_func(context))
rv = context.vars["result"]
if self._undefined_to_none and isinstance(rv, Undefined):
rv = None
@@ -1588,7 +1590,7 @@ class TemplateStream:
def dump(
self,
fp: t.Union[str, t.IO],
fp: t.Union[str, t.IO[bytes]],
encoding: t.Optional[str] = None,
errors: t.Optional[str] = "strict",
) -> None:
@@ -1606,22 +1608,25 @@ class TemplateStream:
if encoding is None:
encoding = "utf-8"
fp = open(fp, "wb")
real_fp: t.IO[bytes] = open(fp, "wb")
close = True
else:
real_fp = fp
try:
if encoding is not None:
iterable = (x.encode(encoding, errors) for x in self) # type: ignore
else:
iterable = self # type: ignore
if hasattr(fp, "writelines"):
fp.writelines(iterable)
if hasattr(real_fp, "writelines"):
real_fp.writelines(iterable)
else:
for item in iterable:
fp.write(item)
real_fp.write(item)
finally:
if close:
fp.close()
real_fp.close()
def disable_buffering(self) -> None:
"""Disable the output buffering."""