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

110 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-09 07:17 +0000

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. Injects `request`, 

26 `session` and `g`. 

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 rv["session"] = reqctx.session 

36 return rv 

37 

38 

39class Environment(BaseEnvironment): 

40 """Works like a regular Jinja2 environment but has some additional 

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

42 name of the blueprint to referenced templates if necessary. 

43 """ 

44 

45 def __init__(self, app: App, **options: t.Any) -> None: 

46 if "loader" not in options: 

47 options["loader"] = app.create_global_jinja_loader() 

48 BaseEnvironment.__init__(self, **options) 

49 self.app = app 

50 

51 

52class DispatchingJinjaLoader(BaseLoader): 

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

54 the blueprint folders. 

55 """ 

56 

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

58 self.app = app 

59 

60 def get_source( # type: ignore 

61 self, environment: Environment, template: str 

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

63 if self.app.config["EXPLAIN_TEMPLATE_LOADING"]: 

64 return self._get_source_explained(environment, template) 

65 return self._get_source_fast(environment, template) 

66 

67 def _get_source_explained( 

68 self, environment: Environment, template: str 

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

70 attempts = [] 

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

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

73 

74 for srcobj, loader in self._iter_loaders(template): 

75 try: 

76 rv = loader.get_source(environment, template) 

77 if trv is None: 

78 trv = rv 

79 except TemplateNotFound: 

80 rv = None 

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

82 

83 from .debughelpers import explain_template_loading_attempts 

84 

85 explain_template_loading_attempts(self.app, template, attempts) 

86 

87 if trv is not None: 

88 return trv 

89 raise TemplateNotFound(template) 

90 

91 def _get_source_fast( 

92 self, environment: Environment, template: str 

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

94 for _srcobj, loader in self._iter_loaders(template): 

95 try: 

96 return loader.get_source(environment, template) 

97 except TemplateNotFound: 

98 continue 

99 raise TemplateNotFound(template) 

100 

101 def _iter_loaders( 

102 self, template: str 

103 ) -> t.Generator[tuple[Scaffold, BaseLoader], None, None]: 

104 loader = self.app.jinja_loader 

105 if loader is not None: 

106 yield self.app, loader 

107 

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

109 loader = blueprint.jinja_loader 

110 if loader is not None: 

111 yield blueprint, loader 

112 

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

114 result = set() 

115 loader = self.app.jinja_loader 

116 if loader is not None: 

117 result.update(loader.list_templates()) 

118 

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

120 loader = blueprint.jinja_loader 

121 if loader is not None: 

122 for template in loader.list_templates(): 

123 result.add(template) 

124 

125 return list(result) 

126 

127 

128def _render(app: Flask, template: Template, context: dict[str, t.Any]) -> str: 

129 app.update_template_context(context) 

130 before_render_template.send( 

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

132 ) 

133 rv = template.render(context) 

134 template_rendered.send( 

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

136 ) 

137 return rv 

138 

139 

140def render_template( 

141 template_name_or_list: str | Template | list[str | Template], 

142 **context: t.Any, 

143) -> str: 

144 """Render a template by name with the given context. 

145 

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

147 a list is given, the first name to exist will be rendered. 

148 :param context: The variables to make available in the template. 

149 """ 

150 app = current_app._get_current_object() # type: ignore[attr-defined] 

151 template = app.jinja_env.get_or_select_template(template_name_or_list) 

152 return _render(app, template, context) 

153 

154 

155def render_template_string(source: str, **context: t.Any) -> str: 

156 """Render a template from the given source string with the given 

157 context. 

158 

159 :param source: The source code of the template to render. 

160 :param context: The variables to make available in the template. 

161 """ 

162 app = current_app._get_current_object() # type: ignore[attr-defined] 

163 template = app.jinja_env.from_string(source) 

164 return _render(app, template, context) 

165 

166 

167def _stream( 

168 app: Flask, template: Template, context: dict[str, t.Any] 

169) -> t.Iterator[str]: 

170 app.update_template_context(context) 

171 before_render_template.send( 

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

173 ) 

174 

175 def generate() -> t.Iterator[str]: 

176 yield from template.generate(context) 

177 template_rendered.send( 

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

179 ) 

180 

181 rv = generate() 

182 

183 # If a request context is active, keep it while generating. 

184 if request: 

185 rv = stream_with_context(rv) 

186 

187 return rv 

188 

189 

190def stream_template( 

191 template_name_or_list: str | Template | list[str | Template], 

192 **context: t.Any, 

193) -> t.Iterator[str]: 

194 """Render a template by name with the given context as a stream. 

195 This returns an iterator of strings, which can be used as a 

196 streaming response from a view. 

197 

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

199 a list is given, the first name to exist will be rendered. 

200 :param context: The variables to make available in the template. 

201 

202 .. versionadded:: 2.2 

203 """ 

204 app = current_app._get_current_object() # type: ignore[attr-defined] 

205 template = app.jinja_env.get_or_select_template(template_name_or_list) 

206 return _stream(app, template, context) 

207 

208 

209def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]: 

210 """Render a template from the given source string with the given 

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

212 be used as a streaming response from a view. 

213 

214 :param source: The source code of the template to render. 

215 :param context: The variables to make available in the template. 

216 

217 .. versionadded:: 2.2 

218 """ 

219 app = current_app._get_current_object() # type: ignore[attr-defined] 

220 template = app.jinja_env.from_string(source) 

221 return _stream(app, template, context)