# -*- coding: utf-8 -*- """The code for async support. Importing this patches Jinja on supported Python versions. """ import asyncio import inspect from functools import update_wrapper from markupsafe import Markup from .environment import TemplateModule from .runtime import LoopContext from .utils import concat from .utils import internalcode from .utils import missing async def concat_async(async_gen): rv = [] async def collect(): async for event in async_gen: rv.append(event) await collect() return concat(rv) async def generate_async(self, *args, **kwargs): vars = dict(*args, **kwargs) try: async for event in self.root_render_func(self.new_context(vars)): yield event except Exception: yield self.environment.handle_exception() def wrap_generate_func(original_generate): def _convert_generator(self, loop, args, kwargs): async_gen = self.generate_async(*args, **kwargs) try: while 1: yield loop.run_until_complete(async_gen.__anext__()) except StopAsyncIteration: pass def generate(self, *args, **kwargs): if not self.environment.is_async: return original_generate(self, *args, **kwargs) return _convert_generator(self, asyncio.get_event_loop(), args, kwargs) return update_wrapper(generate, original_generate) async def render_async(self, *args, **kwargs): if not self.environment.is_async: raise RuntimeError("The environment was not created with async mode enabled.") vars = dict(*args, **kwargs) ctx = self.new_context(vars) try: return await concat_async(self.root_render_func(ctx)) except Exception: return self.environment.handle_exception() def wrap_render_func(original_render): def render(self, *args, **kwargs): if not self.environment.is_async: return original_render(self, *args, **kwargs) loop = asyncio.get_event_loop() return loop.run_until_complete(self.render_async(*args, **kwargs)) return update_wrapper(render, original_render) def wrap_block_reference_call(original_call): @internalcode async def async_call(self): rv = await concat_async(self._stack[self._depth](self._context)) if self._context.eval_ctx.autoescape: rv = Markup(rv) return rv @internalcode def __call__(self): if not self._context.environment.is_async: return original_call(self) return async_call(self) return update_wrapper(__call__, original_call) def wrap_macro_invoke(original_invoke): @internalcode async def async_invoke(self, arguments, autoescape): rv = await self._func(*arguments) if autoescape: rv = Markup(rv) return rv @internalcode def _invoke(self, arguments, autoescape): if not self._environment.is_async: return original_invoke(self, arguments, autoescape) return async_invoke(self, arguments, autoescape) return update_wrapper(_invoke, original_invoke) @internalcode async def get_default_module_async(self): if self._module is not None: return self._module self._module = rv = await self.make_module_async() return rv def wrap_default_module(original_default_module): @internalcode def _get_default_module(self): if self.environment.is_async: raise RuntimeError("Template module attribute is unavailable in async mode") return original_default_module(self) return _get_default_module async def make_module_async(self, vars=None, shared=False, locals=None): context = self.new_context(vars, shared, locals) body_stream = [] async for item in self.root_render_func(context): body_stream.append(item) return TemplateModule(self, context, body_stream) def patch_template(): from . import Template Template.generate = wrap_generate_func(Template.generate) Template.generate_async = update_wrapper(generate_async, Template.generate_async) Template.render_async = update_wrapper(render_async, Template.render_async) Template.render = wrap_render_func(Template.render) Template._get_default_module = wrap_default_module(Template._get_default_module) Template._get_default_module_async = get_default_module_async Template.make_module_async = update_wrapper( make_module_async, Template.make_module_async ) def patch_runtime(): from .runtime import BlockReference, Macro BlockReference.__call__ = wrap_block_reference_call(BlockReference.__call__) Macro._invoke = wrap_macro_invoke(Macro._invoke) def patch_filters(): from .filters import FILTERS from .asyncfilters import ASYNC_FILTERS FILTERS.update(ASYNC_FILTERS) def patch_all(): patch_template() patch_runtime() patch_filters() async def auto_await(value): if inspect.isawaitable(value): return await value return value async def auto_aiter(iterable): if hasattr(iterable, "__aiter__"): async for item in iterable: yield item return for item in iterable: yield item class AsyncLoopContext(LoopContext): _to_iterator = staticmethod(auto_aiter) @property async def length(self): if self._length is not None: return self._length try: self._length = len(self._iterable) except TypeError: iterable = [x async for x in self._iterator] self._iterator = self._to_iterator(iterable) self._length = len(iterable) + self.index + (self._after is not missing) return self._length @property async def revindex0(self): return await self.length - self.index @property async def revindex(self): return await self.length - self.index0 async def _peek_next(self): if self._after is not missing: return self._after try: self._after = await self._iterator.__anext__() except StopAsyncIteration: self._after = missing return self._after @property async def last(self): return await self._peek_next() is missing @property async def nextitem(self): rv = await self._peek_next() if rv is missing: return self._undefined("there is no next item") return rv def __aiter__(self): return self async def __anext__(self): if self._after is not missing: rv = self._after self._after = missing else: rv = await self._iterator.__anext__() self.index0 += 1 self._before = self._current self._current = rv return rv, self async def make_async_loop_context(iterable, undefined, recurse=None, depth0=0): import warnings warnings.warn( "This template must be recompiled with at least Jinja 2.11, or" " it will fail in 3.0.", DeprecationWarning, stacklevel=2, ) return AsyncLoopContext(iterable, undefined, recurse, depth0) patch_all()