codekingpro/portable-devtools
114k
1from __future__ import annotations2 3import typing as t4 5from jinja2 import BaseLoader6from jinja2 import Environment as BaseEnvironment7from jinja2 import Template8from jinja2 import TemplateNotFound9 10from .globals import _cv_app11from .globals import _cv_request12from .globals import current_app13from .globals import request14from .helpers import stream_with_context15from .signals import before_render_template16from .signals import template_rendered17 18if t.TYPE_CHECKING: # pragma: no cover19 from .app import Flask20 from .sansio.app import App21 from .sansio.scaffold import Scaffold22 23 24def _default_template_ctx_processor() -> dict[str, t.Any]:25 """Default template context processor. Replaces the ``request`` and ``g``26 proxies with their concrete objects for faster access.27 """28 appctx = _cv_app.get(None)29 reqctx = _cv_request.get(None)30 rv: dict[str, t.Any] = {}31 if appctx is not None:32 rv["g"] = appctx.g33 if reqctx is not None:34 rv["request"] = reqctx.request35 # The session proxy cannot be replaced, accessing it gets36 # RequestContext.session, which sets session.accessed.37 return rv38 39 40class Environment(BaseEnvironment):41 """Works like a regular Jinja environment but has some additional42 knowledge of how Flask's blueprint works so that it can prepend the43 name of the blueprint to referenced templates if necessary.44 """45 46 def __init__(self, app: App, **options: t.Any) -> None:47 if "loader" not in options:48 options["loader"] = app.create_global_jinja_loader()49 BaseEnvironment.__init__(self, **options)50 self.app = app51 52 53class DispatchingJinjaLoader(BaseLoader):54 """A loader that looks for templates in the application and all55 the blueprint folders.56 """57 58 def __init__(self, app: App) -> None:59 self.app = app60 61 def get_source(62 self, environment: BaseEnvironment, template: str63 ) -> tuple[str, str | None, t.Callable[[], bool] | None]:64 if self.app.config["EXPLAIN_TEMPLATE_LOADING"]:65 return self._get_source_explained(environment, template)66 return self._get_source_fast(environment, template)67 68 def _get_source_explained(69 self, environment: BaseEnvironment, template: str70 ) -> tuple[str, str | None, t.Callable[[], bool] | None]:71 attempts = []72 rv: tuple[str, str | None, t.Callable[[], bool] | None] | None73 trv: None | (tuple[str, str | None, t.Callable[[], bool] | None]) = None74 75 for srcobj, loader in self._iter_loaders(template):76 try:77 rv = loader.get_source(environment, template)78 if trv is None:79 trv = rv80 except TemplateNotFound:81 rv = None82 attempts.append((loader, srcobj, rv))83 84 from .debughelpers import explain_template_loading_attempts85 86 explain_template_loading_attempts(self.app, template, attempts)87 88 if trv is not None:89 return trv90 raise TemplateNotFound(template)91 92 def _get_source_fast(93 self, environment: BaseEnvironment, template: str94 ) -> tuple[str, str | None, t.Callable[[], bool] | None]:95 for _srcobj, loader in self._iter_loaders(template):96 try:97 return loader.get_source(environment, template)98 except TemplateNotFound:99 continue100 raise TemplateNotFound(template)101 102 def _iter_loaders(self, template: str) -> t.Iterator[tuple[Scaffold, BaseLoader]]:103 loader = self.app.jinja_loader104 if loader is not None:105 yield self.app, loader106 107 for blueprint in self.app.iter_blueprints():108 loader = blueprint.jinja_loader109 if loader is not None:110 yield blueprint, loader111 112 def list_templates(self) -> list[str]:113 result = set()114 loader = self.app.jinja_loader115 if loader is not None:116 result.update(loader.list_templates())117 118 for blueprint in self.app.iter_blueprints():119 loader = blueprint.jinja_loader120 if loader is not None:121 for template in loader.list_templates():122 result.add(template)123 124 return list(result)125 126 127def _render(app: Flask, template: Template, context: dict[str, t.Any]) -> str:128 app.update_template_context(context)129 before_render_template.send(130 app, _async_wrapper=app.ensure_sync, template=template, context=context131 )132 rv = template.render(context)133 template_rendered.send(134 app, _async_wrapper=app.ensure_sync, template=template, context=context135 )136 return rv137 138 139def render_template(140 template_name_or_list: str | Template | list[str | Template],141 **context: t.Any,142) -> str:143 """Render a template by name with the given context.144 145 :param template_name_or_list: The name of the template to render. If146 a list is given, the first name to exist will be rendered.147 :param context: The variables to make available in the template.148 """149 app = current_app._get_current_object() # type: ignore[attr-defined]150 template = app.jinja_env.get_or_select_template(template_name_or_list)151 return _render(app, template, context)152 153 154def render_template_string(source: str, **context: t.Any) -> str:155 """Render a template from the given source string with the given156 context.157 158 :param source: The source code of the template to render.159 :param context: The variables to make available in the template.160 """161 app = current_app._get_current_object() # type: ignore[attr-defined]162 template = app.jinja_env.from_string(source)163 return _render(app, template, context)164 165 166def _stream(167 app: Flask, template: Template, context: dict[str, t.Any]168) -> t.Iterator[str]:169 app.update_template_context(context)170 before_render_template.send(171 app, _async_wrapper=app.ensure_sync, template=template, context=context172 )173 174 def generate() -> t.Iterator[str]:175 yield from template.generate(context)176 template_rendered.send(177 app, _async_wrapper=app.ensure_sync, template=template, context=context178 )179 180 rv = generate()181 182 # If a request context is active, keep it while generating.183 if request:184 rv = stream_with_context(rv)185 186 return rv187 188 189def stream_template(190 template_name_or_list: str | Template | list[str | Template],191 **context: t.Any,192) -> t.Iterator[str]:193 """Render a template by name with the given context as a stream.194 This returns an iterator of strings, which can be used as a195 streaming response from a view.196 197 :param template_name_or_list: The name of the template to render. If198 a list is given, the first name to exist will be rendered.199 :param context: The variables to make available in the template.200 201 .. versionadded:: 2.2202 """203 app = current_app._get_current_object() # type: ignore[attr-defined]204 template = app.jinja_env.get_or_select_template(template_name_or_list)205 return _stream(app, template, context)206 207 208def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]:209 """Render a template from the given source string with the given210 context as a stream. This returns an iterator of strings, which can211 be used as a streaming response from a view.212 213 :param source: The source code of the template to render.214 :param context: The variables to make available in the template.215 216 .. versionadded:: 2.2217 """218 app = current_app._get_current_object() # type: ignore[attr-defined]219 template = app.jinja_env.from_string(source)220 return _stream(app, template, context)221 