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

110 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-09 06:08 +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 .scaffold import Scaffold 

21 

22 

23def _default_template_ctx_processor() -> dict[str, t.Any]: 

24 """Default template context processor. Injects `request`, 

25 `session` and `g`. 

26 """ 

27 appctx = _cv_app.get(None) 

28 reqctx = _cv_request.get(None) 

29 rv: dict[str, t.Any] = {} 

30 if appctx is not None: 

31 rv["g"] = appctx.g 

32 if reqctx is not None: 

33 rv["request"] = reqctx.request 

34 rv["session"] = reqctx.session 

35 return rv 

36 

37 

38class Environment(BaseEnvironment): 

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

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

41 name of the blueprint to referenced templates if necessary. 

42 """ 

43 

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

45 if "loader" not in options: 

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

47 BaseEnvironment.__init__(self, **options) 

48 self.app = app 

49 

50 

51class DispatchingJinjaLoader(BaseLoader): 

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

53 the blueprint folders. 

54 """ 

55 

56 def __init__(self, app: Flask) -> None: 

57 self.app = app 

58 

59 def get_source( # type: ignore 

60 self, environment: Environment, template: str 

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

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

63 return self._get_source_explained(environment, template) 

64 return self._get_source_fast(environment, template) 

65 

66 def _get_source_explained( 

67 self, environment: Environment, template: str 

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

69 attempts = [] 

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

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

72 

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

74 try: 

75 rv = loader.get_source(environment, template) 

76 if trv is None: 

77 trv = rv 

78 except TemplateNotFound: 

79 rv = None 

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

81 

82 from .debughelpers import explain_template_loading_attempts 

83 

84 explain_template_loading_attempts(self.app, template, attempts) 

85 

86 if trv is not None: 

87 return trv 

88 raise TemplateNotFound(template) 

89 

90 def _get_source_fast( 

91 self, environment: Environment, template: str 

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

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

94 try: 

95 return loader.get_source(environment, template) 

96 except TemplateNotFound: 

97 continue 

98 raise TemplateNotFound(template) 

99 

100 def _iter_loaders( 

101 self, template: str 

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

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)