1*635a8641SAndroid Build Coastguard Worker# -*- coding: utf-8 -*- 2*635a8641SAndroid Build Coastguard Worker""" 3*635a8641SAndroid Build Coastguard Worker jinja2.asyncsupport 4*635a8641SAndroid Build Coastguard Worker ~~~~~~~~~~~~~~~~~~~ 5*635a8641SAndroid Build Coastguard Worker 6*635a8641SAndroid Build Coastguard Worker Has all the code for async support which is implemented as a patch 7*635a8641SAndroid Build Coastguard Worker for supported Python versions. 8*635a8641SAndroid Build Coastguard Worker 9*635a8641SAndroid Build Coastguard Worker :copyright: (c) 2017 by the Jinja Team. 10*635a8641SAndroid Build Coastguard Worker :license: BSD, see LICENSE for more details. 11*635a8641SAndroid Build Coastguard Worker""" 12*635a8641SAndroid Build Coastguard Workerimport sys 13*635a8641SAndroid Build Coastguard Workerimport asyncio 14*635a8641SAndroid Build Coastguard Workerimport inspect 15*635a8641SAndroid Build Coastguard Workerfrom functools import update_wrapper 16*635a8641SAndroid Build Coastguard Worker 17*635a8641SAndroid Build Coastguard Workerfrom jinja2.utils import concat, internalcode, Markup 18*635a8641SAndroid Build Coastguard Workerfrom jinja2.environment import TemplateModule 19*635a8641SAndroid Build Coastguard Workerfrom jinja2.runtime import LoopContextBase, _last_iteration 20*635a8641SAndroid Build Coastguard Worker 21*635a8641SAndroid Build Coastguard Worker 22*635a8641SAndroid Build Coastguard Workerasync def concat_async(async_gen): 23*635a8641SAndroid Build Coastguard Worker rv = [] 24*635a8641SAndroid Build Coastguard Worker async def collect(): 25*635a8641SAndroid Build Coastguard Worker async for event in async_gen: 26*635a8641SAndroid Build Coastguard Worker rv.append(event) 27*635a8641SAndroid Build Coastguard Worker await collect() 28*635a8641SAndroid Build Coastguard Worker return concat(rv) 29*635a8641SAndroid Build Coastguard Worker 30*635a8641SAndroid Build Coastguard Worker 31*635a8641SAndroid Build Coastguard Workerasync def generate_async(self, *args, **kwargs): 32*635a8641SAndroid Build Coastguard Worker vars = dict(*args, **kwargs) 33*635a8641SAndroid Build Coastguard Worker try: 34*635a8641SAndroid Build Coastguard Worker async for event in self.root_render_func(self.new_context(vars)): 35*635a8641SAndroid Build Coastguard Worker yield event 36*635a8641SAndroid Build Coastguard Worker except Exception: 37*635a8641SAndroid Build Coastguard Worker exc_info = sys.exc_info() 38*635a8641SAndroid Build Coastguard Worker else: 39*635a8641SAndroid Build Coastguard Worker return 40*635a8641SAndroid Build Coastguard Worker yield self.environment.handle_exception(exc_info, True) 41*635a8641SAndroid Build Coastguard Worker 42*635a8641SAndroid Build Coastguard Worker 43*635a8641SAndroid Build Coastguard Workerdef wrap_generate_func(original_generate): 44*635a8641SAndroid Build Coastguard Worker def _convert_generator(self, loop, args, kwargs): 45*635a8641SAndroid Build Coastguard Worker async_gen = self.generate_async(*args, **kwargs) 46*635a8641SAndroid Build Coastguard Worker try: 47*635a8641SAndroid Build Coastguard Worker while 1: 48*635a8641SAndroid Build Coastguard Worker yield loop.run_until_complete(async_gen.__anext__()) 49*635a8641SAndroid Build Coastguard Worker except StopAsyncIteration: 50*635a8641SAndroid Build Coastguard Worker pass 51*635a8641SAndroid Build Coastguard Worker def generate(self, *args, **kwargs): 52*635a8641SAndroid Build Coastguard Worker if not self.environment.is_async: 53*635a8641SAndroid Build Coastguard Worker return original_generate(self, *args, **kwargs) 54*635a8641SAndroid Build Coastguard Worker return _convert_generator(self, asyncio.get_event_loop(), args, kwargs) 55*635a8641SAndroid Build Coastguard Worker return update_wrapper(generate, original_generate) 56*635a8641SAndroid Build Coastguard Worker 57*635a8641SAndroid Build Coastguard Worker 58*635a8641SAndroid Build Coastguard Workerasync def render_async(self, *args, **kwargs): 59*635a8641SAndroid Build Coastguard Worker if not self.environment.is_async: 60*635a8641SAndroid Build Coastguard Worker raise RuntimeError('The environment was not created with async mode ' 61*635a8641SAndroid Build Coastguard Worker 'enabled.') 62*635a8641SAndroid Build Coastguard Worker 63*635a8641SAndroid Build Coastguard Worker vars = dict(*args, **kwargs) 64*635a8641SAndroid Build Coastguard Worker ctx = self.new_context(vars) 65*635a8641SAndroid Build Coastguard Worker 66*635a8641SAndroid Build Coastguard Worker try: 67*635a8641SAndroid Build Coastguard Worker return await concat_async(self.root_render_func(ctx)) 68*635a8641SAndroid Build Coastguard Worker except Exception: 69*635a8641SAndroid Build Coastguard Worker exc_info = sys.exc_info() 70*635a8641SAndroid Build Coastguard Worker return self.environment.handle_exception(exc_info, True) 71*635a8641SAndroid Build Coastguard Worker 72*635a8641SAndroid Build Coastguard Worker 73*635a8641SAndroid Build Coastguard Workerdef wrap_render_func(original_render): 74*635a8641SAndroid Build Coastguard Worker def render(self, *args, **kwargs): 75*635a8641SAndroid Build Coastguard Worker if not self.environment.is_async: 76*635a8641SAndroid Build Coastguard Worker return original_render(self, *args, **kwargs) 77*635a8641SAndroid Build Coastguard Worker loop = asyncio.get_event_loop() 78*635a8641SAndroid Build Coastguard Worker return loop.run_until_complete(self.render_async(*args, **kwargs)) 79*635a8641SAndroid Build Coastguard Worker return update_wrapper(render, original_render) 80*635a8641SAndroid Build Coastguard Worker 81*635a8641SAndroid Build Coastguard Worker 82*635a8641SAndroid Build Coastguard Workerdef wrap_block_reference_call(original_call): 83*635a8641SAndroid Build Coastguard Worker @internalcode 84*635a8641SAndroid Build Coastguard Worker async def async_call(self): 85*635a8641SAndroid Build Coastguard Worker rv = await concat_async(self._stack[self._depth](self._context)) 86*635a8641SAndroid Build Coastguard Worker if self._context.eval_ctx.autoescape: 87*635a8641SAndroid Build Coastguard Worker rv = Markup(rv) 88*635a8641SAndroid Build Coastguard Worker return rv 89*635a8641SAndroid Build Coastguard Worker 90*635a8641SAndroid Build Coastguard Worker @internalcode 91*635a8641SAndroid Build Coastguard Worker def __call__(self): 92*635a8641SAndroid Build Coastguard Worker if not self._context.environment.is_async: 93*635a8641SAndroid Build Coastguard Worker return original_call(self) 94*635a8641SAndroid Build Coastguard Worker return async_call(self) 95*635a8641SAndroid Build Coastguard Worker 96*635a8641SAndroid Build Coastguard Worker return update_wrapper(__call__, original_call) 97*635a8641SAndroid Build Coastguard Worker 98*635a8641SAndroid Build Coastguard Worker 99*635a8641SAndroid Build Coastguard Workerdef wrap_macro_invoke(original_invoke): 100*635a8641SAndroid Build Coastguard Worker @internalcode 101*635a8641SAndroid Build Coastguard Worker async def async_invoke(self, arguments, autoescape): 102*635a8641SAndroid Build Coastguard Worker rv = await self._func(*arguments) 103*635a8641SAndroid Build Coastguard Worker if autoescape: 104*635a8641SAndroid Build Coastguard Worker rv = Markup(rv) 105*635a8641SAndroid Build Coastguard Worker return rv 106*635a8641SAndroid Build Coastguard Worker 107*635a8641SAndroid Build Coastguard Worker @internalcode 108*635a8641SAndroid Build Coastguard Worker def _invoke(self, arguments, autoescape): 109*635a8641SAndroid Build Coastguard Worker if not self._environment.is_async: 110*635a8641SAndroid Build Coastguard Worker return original_invoke(self, arguments, autoescape) 111*635a8641SAndroid Build Coastguard Worker return async_invoke(self, arguments, autoescape) 112*635a8641SAndroid Build Coastguard Worker return update_wrapper(_invoke, original_invoke) 113*635a8641SAndroid Build Coastguard Worker 114*635a8641SAndroid Build Coastguard Worker 115*635a8641SAndroid Build Coastguard Worker@internalcode 116*635a8641SAndroid Build Coastguard Workerasync def get_default_module_async(self): 117*635a8641SAndroid Build Coastguard Worker if self._module is not None: 118*635a8641SAndroid Build Coastguard Worker return self._module 119*635a8641SAndroid Build Coastguard Worker self._module = rv = await self.make_module_async() 120*635a8641SAndroid Build Coastguard Worker return rv 121*635a8641SAndroid Build Coastguard Worker 122*635a8641SAndroid Build Coastguard Worker 123*635a8641SAndroid Build Coastguard Workerdef wrap_default_module(original_default_module): 124*635a8641SAndroid Build Coastguard Worker @internalcode 125*635a8641SAndroid Build Coastguard Worker def _get_default_module(self): 126*635a8641SAndroid Build Coastguard Worker if self.environment.is_async: 127*635a8641SAndroid Build Coastguard Worker raise RuntimeError('Template module attribute is unavailable ' 128*635a8641SAndroid Build Coastguard Worker 'in async mode') 129*635a8641SAndroid Build Coastguard Worker return original_default_module(self) 130*635a8641SAndroid Build Coastguard Worker return _get_default_module 131*635a8641SAndroid Build Coastguard Worker 132*635a8641SAndroid Build Coastguard Worker 133*635a8641SAndroid Build Coastguard Workerasync def make_module_async(self, vars=None, shared=False, locals=None): 134*635a8641SAndroid Build Coastguard Worker context = self.new_context(vars, shared, locals) 135*635a8641SAndroid Build Coastguard Worker body_stream = [] 136*635a8641SAndroid Build Coastguard Worker async for item in self.root_render_func(context): 137*635a8641SAndroid Build Coastguard Worker body_stream.append(item) 138*635a8641SAndroid Build Coastguard Worker return TemplateModule(self, context, body_stream) 139*635a8641SAndroid Build Coastguard Worker 140*635a8641SAndroid Build Coastguard Worker 141*635a8641SAndroid Build Coastguard Workerdef patch_template(): 142*635a8641SAndroid Build Coastguard Worker from jinja2 import Template 143*635a8641SAndroid Build Coastguard Worker Template.generate = wrap_generate_func(Template.generate) 144*635a8641SAndroid Build Coastguard Worker Template.generate_async = update_wrapper( 145*635a8641SAndroid Build Coastguard Worker generate_async, Template.generate_async) 146*635a8641SAndroid Build Coastguard Worker Template.render_async = update_wrapper( 147*635a8641SAndroid Build Coastguard Worker render_async, Template.render_async) 148*635a8641SAndroid Build Coastguard Worker Template.render = wrap_render_func(Template.render) 149*635a8641SAndroid Build Coastguard Worker Template._get_default_module = wrap_default_module( 150*635a8641SAndroid Build Coastguard Worker Template._get_default_module) 151*635a8641SAndroid Build Coastguard Worker Template._get_default_module_async = get_default_module_async 152*635a8641SAndroid Build Coastguard Worker Template.make_module_async = update_wrapper( 153*635a8641SAndroid Build Coastguard Worker make_module_async, Template.make_module_async) 154*635a8641SAndroid Build Coastguard Worker 155*635a8641SAndroid Build Coastguard Worker 156*635a8641SAndroid Build Coastguard Workerdef patch_runtime(): 157*635a8641SAndroid Build Coastguard Worker from jinja2.runtime import BlockReference, Macro 158*635a8641SAndroid Build Coastguard Worker BlockReference.__call__ = wrap_block_reference_call( 159*635a8641SAndroid Build Coastguard Worker BlockReference.__call__) 160*635a8641SAndroid Build Coastguard Worker Macro._invoke = wrap_macro_invoke(Macro._invoke) 161*635a8641SAndroid Build Coastguard Worker 162*635a8641SAndroid Build Coastguard Worker 163*635a8641SAndroid Build Coastguard Workerdef patch_filters(): 164*635a8641SAndroid Build Coastguard Worker from jinja2.filters import FILTERS 165*635a8641SAndroid Build Coastguard Worker from jinja2.asyncfilters import ASYNC_FILTERS 166*635a8641SAndroid Build Coastguard Worker FILTERS.update(ASYNC_FILTERS) 167*635a8641SAndroid Build Coastguard Worker 168*635a8641SAndroid Build Coastguard Worker 169*635a8641SAndroid Build Coastguard Workerdef patch_all(): 170*635a8641SAndroid Build Coastguard Worker patch_template() 171*635a8641SAndroid Build Coastguard Worker patch_runtime() 172*635a8641SAndroid Build Coastguard Worker patch_filters() 173*635a8641SAndroid Build Coastguard Worker 174*635a8641SAndroid Build Coastguard Worker 175*635a8641SAndroid Build Coastguard Workerasync def auto_await(value): 176*635a8641SAndroid Build Coastguard Worker if inspect.isawaitable(value): 177*635a8641SAndroid Build Coastguard Worker return await value 178*635a8641SAndroid Build Coastguard Worker return value 179*635a8641SAndroid Build Coastguard Worker 180*635a8641SAndroid Build Coastguard Worker 181*635a8641SAndroid Build Coastguard Workerasync def auto_aiter(iterable): 182*635a8641SAndroid Build Coastguard Worker if hasattr(iterable, '__aiter__'): 183*635a8641SAndroid Build Coastguard Worker async for item in iterable: 184*635a8641SAndroid Build Coastguard Worker yield item 185*635a8641SAndroid Build Coastguard Worker return 186*635a8641SAndroid Build Coastguard Worker for item in iterable: 187*635a8641SAndroid Build Coastguard Worker yield item 188*635a8641SAndroid Build Coastguard Worker 189*635a8641SAndroid Build Coastguard Worker 190*635a8641SAndroid Build Coastguard Workerclass AsyncLoopContext(LoopContextBase): 191*635a8641SAndroid Build Coastguard Worker 192*635a8641SAndroid Build Coastguard Worker def __init__(self, async_iterator, undefined, after, length, recurse=None, 193*635a8641SAndroid Build Coastguard Worker depth0=0): 194*635a8641SAndroid Build Coastguard Worker LoopContextBase.__init__(self, undefined, recurse, depth0) 195*635a8641SAndroid Build Coastguard Worker self._async_iterator = async_iterator 196*635a8641SAndroid Build Coastguard Worker self._after = after 197*635a8641SAndroid Build Coastguard Worker self._length = length 198*635a8641SAndroid Build Coastguard Worker 199*635a8641SAndroid Build Coastguard Worker @property 200*635a8641SAndroid Build Coastguard Worker def length(self): 201*635a8641SAndroid Build Coastguard Worker if self._length is None: 202*635a8641SAndroid Build Coastguard Worker raise TypeError('Loop length for some iterators cannot be ' 203*635a8641SAndroid Build Coastguard Worker 'lazily calculated in async mode') 204*635a8641SAndroid Build Coastguard Worker return self._length 205*635a8641SAndroid Build Coastguard Worker 206*635a8641SAndroid Build Coastguard Worker def __aiter__(self): 207*635a8641SAndroid Build Coastguard Worker return AsyncLoopContextIterator(self) 208*635a8641SAndroid Build Coastguard Worker 209*635a8641SAndroid Build Coastguard Worker 210*635a8641SAndroid Build Coastguard Workerclass AsyncLoopContextIterator(object): 211*635a8641SAndroid Build Coastguard Worker __slots__ = ('context',) 212*635a8641SAndroid Build Coastguard Worker 213*635a8641SAndroid Build Coastguard Worker def __init__(self, context): 214*635a8641SAndroid Build Coastguard Worker self.context = context 215*635a8641SAndroid Build Coastguard Worker 216*635a8641SAndroid Build Coastguard Worker def __aiter__(self): 217*635a8641SAndroid Build Coastguard Worker return self 218*635a8641SAndroid Build Coastguard Worker 219*635a8641SAndroid Build Coastguard Worker async def __anext__(self): 220*635a8641SAndroid Build Coastguard Worker ctx = self.context 221*635a8641SAndroid Build Coastguard Worker ctx.index0 += 1 222*635a8641SAndroid Build Coastguard Worker if ctx._after is _last_iteration: 223*635a8641SAndroid Build Coastguard Worker raise StopAsyncIteration() 224*635a8641SAndroid Build Coastguard Worker ctx._before = ctx._current 225*635a8641SAndroid Build Coastguard Worker ctx._current = ctx._after 226*635a8641SAndroid Build Coastguard Worker try: 227*635a8641SAndroid Build Coastguard Worker ctx._after = await ctx._async_iterator.__anext__() 228*635a8641SAndroid Build Coastguard Worker except StopAsyncIteration: 229*635a8641SAndroid Build Coastguard Worker ctx._after = _last_iteration 230*635a8641SAndroid Build Coastguard Worker return ctx._current, ctx 231*635a8641SAndroid Build Coastguard Worker 232*635a8641SAndroid Build Coastguard Worker 233*635a8641SAndroid Build Coastguard Workerasync def make_async_loop_context(iterable, undefined, recurse=None, depth0=0): 234*635a8641SAndroid Build Coastguard Worker # Length is more complicated and less efficient in async mode. The 235*635a8641SAndroid Build Coastguard Worker # reason for this is that we cannot know if length will be used 236*635a8641SAndroid Build Coastguard Worker # upfront but because length is a property we cannot lazily execute it 237*635a8641SAndroid Build Coastguard Worker # later. This means that we need to buffer it up and measure :( 238*635a8641SAndroid Build Coastguard Worker # 239*635a8641SAndroid Build Coastguard Worker # We however only do this for actual iterators, not for async 240*635a8641SAndroid Build Coastguard Worker # iterators as blocking here does not seem like the best idea in the 241*635a8641SAndroid Build Coastguard Worker # world. 242*635a8641SAndroid Build Coastguard Worker try: 243*635a8641SAndroid Build Coastguard Worker length = len(iterable) 244*635a8641SAndroid Build Coastguard Worker except (TypeError, AttributeError): 245*635a8641SAndroid Build Coastguard Worker if not hasattr(iterable, '__aiter__'): 246*635a8641SAndroid Build Coastguard Worker iterable = tuple(iterable) 247*635a8641SAndroid Build Coastguard Worker length = len(iterable) 248*635a8641SAndroid Build Coastguard Worker else: 249*635a8641SAndroid Build Coastguard Worker length = None 250*635a8641SAndroid Build Coastguard Worker async_iterator = auto_aiter(iterable) 251*635a8641SAndroid Build Coastguard Worker try: 252*635a8641SAndroid Build Coastguard Worker after = await async_iterator.__anext__() 253*635a8641SAndroid Build Coastguard Worker except StopAsyncIteration: 254*635a8641SAndroid Build Coastguard Worker after = _last_iteration 255*635a8641SAndroid Build Coastguard Worker return AsyncLoopContext(async_iterator, undefined, after, length, recurse, 256*635a8641SAndroid Build Coastguard Worker depth0) 257