from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, TYPE_CHECKING, Union from jinja2 import BaseLoader, Environment as BaseEnvironment, Template, TemplateNotFound from .ctx import has_app_context, has_request_context from .globals import _app_ctx_stack, _request_ctx_stack, current_app from .signals import before_render_template, template_rendered if TYPE_CHECKING: from .app import Quart # noqa class Environment(BaseEnvironment): """Quart specific Jinja2 Environment. This changes the default Jinja2 loader to use the DispatchingJinjaLoader, and enables async Jinja by default. """ def __init__(self, app: 'Quart', **options: Any) -> None: """Create a Quart specific Jinja2 Environment. Arguments: app: The Quart app to bind to. options: The standard Jinja2 Environment options. """ if 'loader' not in options: options['loader'] = app.create_global_jinja_loader() options['enable_async'] = True super().__init__(**options) class DispatchingJinjaLoader(BaseLoader): """Quart specific Jinja2 Loader. This changes the default sourcing to consider the app and blueprints. """ def __init__(self, app: 'Quart') -> None: self.app = app def get_source( self, environment: Environment, template: str, ) -> Tuple[str, Optional[str], Callable]: """Returns the template source from the environment. This considers the loaders on the :attr:`app` and blueprints. """ for loader in self._loaders(): try: return loader.get_source(environment, template) except TemplateNotFound: continue raise TemplateNotFound(template) def _loaders(self) -> Generator[BaseLoader, None, None]: loader = self.app.jinja_loader if loader is not None: yield loader for blueprint in self.app.iter_blueprints(): loader = blueprint.jinja_loader if loader is not None: yield loader def list_templates(self) -> List[str]: """Returns a list of all avilable templates in environment. This considers the loaders on the :attr:`app` and blueprints. """ result = set() for loader in self._loaders(): for template in loader.list_templates(): result.add(str(template)) return list(result) async def render_template(template_name_or_list: Union[str, List[str]], **context: Any) -> str: """Render the template with the context given. Arguments: template_name_or_list: Template name to render of a list of possible template names. context: The variables to pass to the template. """ await current_app.update_template_context(context) template = current_app.jinja_env.get_or_select_template(template_name_or_list) return await _render(template, context) async def render_template_string(source: str, **context: Any) -> str: """Render the template source with the context given. Arguments: source: The template source code. context: The variables to pass to the template. """ await current_app.update_template_context(context) template = current_app.jinja_env.from_string(source) return await _render(template, context) async def _render(template: Template, context: dict) -> str: app = current_app._get_current_object() await before_render_template.send(app, template=template, context=context) rendered_template = await template.render_async(context) await template_rendered.send(app, template=template, context=context) return rendered_template async def _default_template_context_processor() -> Dict[str, Any]: context = {} if has_app_context(): context['g'] = _app_ctx_stack.top.g if has_request_context(): context['request'] = _request_ctx_stack.top.request context['session'] = _request_ctx_stack.top.session return context