# -*- coding: utf-8 -*- """ jinja2.asyncsupport ~~~~~~~~~~~~~~~~~~~ Has all the code for async support which is implemented as a patch for supported Python versions. :copyright: (c) 2017 by the Jinja Team. :license: BSD, see LICENSE for more details. """ import sys import asyncio import inspect from functools import update_wrapper from jinja2.utils import concat, internalcode, Markup from jinja2.environment import TemplateModule from jinja2.runtime import LoopContextBase, _last_iteration 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: exc_info = sys.exc_info() else: return yield self.environment.handle_exception(exc_info, True) 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: exc_info = sys.exc_info() return self.environment.handle_exception(exc_info, True) 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 jinja2 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 jinja2.runtime import BlockReference, Macro BlockReference.__call__ = wrap_block_reference_call( BlockReference.__call__) Macro._invoke = wrap_macro_invoke(Macro._invoke) def patch_filters(): from jinja2.filters import FILTERS from jinja2.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(LoopContextBase): def __init__(self, async_iterator, undefined, after, length, recurse=None, depth0=0): LoopContextBase.__init__(self, undefined, recurse, depth0) self._async_iterator = async_iterator self._after = after self._length = length @property def length(self): if self._length is None: raise TypeError('Loop length for some iterators cannot be ' 'lazily calculated in async mode') return self._length def __aiter__(self): return AsyncLoopContextIterator(self) class AsyncLoopContextIterator(object): __slots__ = ('context',) def __init__(self, context): self.context = context def __aiter__(self): return self async def __anext__(self): ctx = self.context ctx.index0 += 1 if ctx._after is _last_iteration: raise StopAsyncIteration() ctx._before = ctx._current ctx._current = ctx._after try: ctx._after = await ctx._async_iterator.__anext__() except StopAsyncIteration: ctx._after = _last_iteration return ctx._current, ctx async def make_async_loop_context(iterable, undefined, recurse=None, depth0=0): # Length is more complicated and less efficient in async mode. The # reason for this is that we cannot know if length will be used # upfront but because length is a property we cannot lazily execute it # later. This means that we need to buffer it up and measure :( # # We however only do this for actual iterators, not for async # iterators as blocking here does not seem like the best idea in the # world. try: length = len(iterable) except (TypeError, AttributeError): if not hasattr(iterable, '__aiter__'): iterable = tuple(iterable) length = len(iterable) else: length = None async_iterator = auto_aiter(iterable) try: after = await async_iterator.__anext__() except StopAsyncIteration: after = _last_iteration return AsyncLoopContext(async_iterator, undefined, after, length, recurse, depth0)