Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/flask/templating.py: 27%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

109 statements  

1from __future__ import annotations 

2 

3import typing as t 

4 

5from jinja2 import BaseLoader 

6from jinja2 import Environment as BaseEnvironment 

7from jinja2 import Template 

8from jinja2 import TemplateNotFound 

9 

10from .globals import _cv_app 

11from .globals import _cv_request 

12from .globals import current_app 

13from .globals import request 

14from .helpers import stream_with_context 

15from .signals import before_render_template 

16from .signals import template_rendered 

17 

18if t.TYPE_CHECKING: # pragma: no cover 

19 from .app import Flask 

20 from .sansio.app import App 

21 from .sansio.scaffold import Scaffold 

22 

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.g 

33 if reqctx is not None: 

34 rv["request"] = reqctx.request 

35 # The session proxy cannot be replaced, accessing it gets 

36 # RequestContext.session, which sets session.accessed. 

37 return rv 

38 

39 

40class Environment(BaseEnvironment): 

41 """Works like a regular Jinja environment but has some additional 

42 knowledge of how Flask's blueprint works so that it can prepend the 

43 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 = app 

51 

52 

53class DispatchingJinjaLoader(BaseLoader): 

54 """A loader that looks for templates in the application and all 

55 the blueprint folders. 

56 """ 

57 

58 def __init__(self, app: App) -> None: 

59 self.app = app 

60 

61 def get_source( 

62 self, environment: BaseEnvironment, template: str 

63 ) -> 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: str 

70 ) -> tuple[str, str | None, t.Callable[[], bool] | None]: 

71 attempts = [] 

72 rv: tuple[str, str | None, t.Callable[[], bool] | None] | None 

73 trv: None | (tuple[str, str | None, t.Callable[[], bool] | None]) = None 

74 

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 = rv 

80 except TemplateNotFound: 

81 rv = None 

82 attempts.append((loader, srcobj, rv)) 

83 

84 from .debughelpers import explain_template_loading_attempts 

85 

86 explain_template_loading_attempts(self.app, template, attempts) 

87 

88 if trv is not None: 

89 return trv 

90 raise TemplateNotFound(template) 

91 

92 def _get_source_fast( 

93 self, environment: BaseEnvironment, template: str 

94 ) -> 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 continue 

100 raise TemplateNotFound(template) 

101 

102 def _iter_loaders(self, template: str) -> t.Iterator[tuple[Scaffold, BaseLoader]]: 

103 loader = self.app.jinja_loader 

104 if loader is not None: 

105 yield self.app, loader 

106 

107 for blueprint in self.app.iter_blueprints(): 

108 loader = blueprint.jinja_loader 

109 if loader is not None: 

110 yield blueprint, loader 

111 

112 def list_templates(self) -> list[str]: 

113 result = set() 

114 loader = self.app.jinja_loader 

115 if loader is not None: 

116 result.update(loader.list_templates()) 

117 

118 for blueprint in self.app.iter_blueprints(): 

119 loader = blueprint.jinja_loader 

120 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=context 

131 ) 

132 rv = template.render(context) 

133 template_rendered.send( 

134 app, _async_wrapper=app.ensure_sync, template=template, context=context 

135 ) 

136 return rv 

137 

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. If 

146 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 given 

156 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=context 

172 ) 

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=context 

178 ) 

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 rv 

187 

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 a 

195 streaming response from a view. 

196 

197 :param template_name_or_list: The name of the template to render. If 

198 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.2 

202 """ 

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 given 

210 context as a stream. This returns an iterator of strings, which can 

211 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.2 

217 """ 

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)