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

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

95 statements  

1from __future__ import annotations 

2 

3import typing 

4import warnings 

5from os import PathLike 

6 

7from starlette.background import BackgroundTask 

8from starlette.datastructures import URL 

9from starlette.requests import Request 

10from starlette.responses import HTMLResponse 

11from starlette.types import Receive, Scope, Send 

12 

13try: 

14 import jinja2 

15 

16 # @contextfunction was renamed to @pass_context in Jinja 3.0, and was removed in 3.1 

17 # hence we try to get pass_context (most installs will be >=3.1) 

18 # and fall back to contextfunction, 

19 # adding a type ignore for mypy to let us access an attribute that may not exist 

20 if hasattr(jinja2, "pass_context"): 

21 pass_context = jinja2.pass_context 

22 else: # pragma: nocover 

23 pass_context = jinja2.contextfunction # type: ignore[attr-defined] 

24except ModuleNotFoundError: # pragma: nocover 

25 jinja2 = None # type: ignore[assignment] 

26 

27 

28class _TemplateResponse(HTMLResponse): 

29 def __init__( 

30 self, 

31 template: typing.Any, 

32 context: dict[str, typing.Any], 

33 status_code: int = 200, 

34 headers: typing.Mapping[str, str] | None = None, 

35 media_type: str | None = None, 

36 background: BackgroundTask | None = None, 

37 ): 

38 self.template = template 

39 self.context = context 

40 content = template.render(context) 

41 super().__init__(content, status_code, headers, media_type, background) 

42 

43 async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: 

44 request = self.context.get("request", {}) 

45 extensions = request.get("extensions", {}) 

46 if "http.response.debug" in extensions: 

47 await send( 

48 { 

49 "type": "http.response.debug", 

50 "info": { 

51 "template": self.template, 

52 "context": self.context, 

53 }, 

54 } 

55 ) 

56 await super().__call__(scope, receive, send) 

57 

58 

59class Jinja2Templates: 

60 """ 

61 templates = Jinja2Templates("templates") 

62 

63 return templates.TemplateResponse("index.html", {"request": request}) 

64 """ 

65 

66 @typing.overload 

67 def __init__( 

68 self, 

69 directory: str | PathLike[str] | typing.Sequence[str | PathLike[str]], 

70 *, 

71 context_processors: list[typing.Callable[[Request], dict[str, typing.Any]]] | None = None, 

72 **env_options: typing.Any, 

73 ) -> None: ... 

74 

75 @typing.overload 

76 def __init__( 

77 self, 

78 *, 

79 env: jinja2.Environment, 

80 context_processors: list[typing.Callable[[Request], dict[str, typing.Any]]] | None = None, 

81 ) -> None: ... 

82 

83 def __init__( 

84 self, 

85 directory: str | PathLike[str] | typing.Sequence[str | PathLike[str]] | None = None, 

86 *, 

87 context_processors: list[typing.Callable[[Request], dict[str, typing.Any]]] | None = None, 

88 env: jinja2.Environment | None = None, 

89 **env_options: typing.Any, 

90 ) -> None: 

91 if env_options: 

92 warnings.warn( 

93 "Extra environment options are deprecated. Use a preconfigured jinja2.Environment instead.", 

94 DeprecationWarning, 

95 ) 

96 assert jinja2 is not None, "jinja2 must be installed to use Jinja2Templates" 

97 assert bool(directory) ^ bool(env), "either 'directory' or 'env' arguments must be passed" 

98 self.context_processors = context_processors or [] 

99 if directory is not None: 

100 self.env = self._create_env(directory, **env_options) 

101 elif env is not None: 

102 self.env = env 

103 

104 self._setup_env_defaults(self.env) 

105 

106 def _create_env( 

107 self, 

108 directory: str | PathLike[str] | typing.Sequence[str | PathLike[str]], 

109 **env_options: typing.Any, 

110 ) -> jinja2.Environment: 

111 loader = jinja2.FileSystemLoader(directory) 

112 env_options.setdefault("loader", loader) 

113 env_options.setdefault("autoescape", True) 

114 

115 return jinja2.Environment(**env_options) 

116 

117 def _setup_env_defaults(self, env: jinja2.Environment) -> None: 

118 @pass_context 

119 def url_for( 

120 context: dict[str, typing.Any], 

121 name: str, 

122 /, 

123 **path_params: typing.Any, 

124 ) -> URL: 

125 request: Request = context["request"] 

126 return request.url_for(name, **path_params) 

127 

128 env.globals.setdefault("url_for", url_for) 

129 

130 def get_template(self, name: str) -> jinja2.Template: 

131 return self.env.get_template(name) 

132 

133 @typing.overload 

134 def TemplateResponse( 

135 self, 

136 request: Request, 

137 name: str, 

138 context: dict[str, typing.Any] | None = None, 

139 status_code: int = 200, 

140 headers: typing.Mapping[str, str] | None = None, 

141 media_type: str | None = None, 

142 background: BackgroundTask | None = None, 

143 ) -> _TemplateResponse: ... 

144 

145 @typing.overload 

146 def TemplateResponse( 

147 self, 

148 name: str, 

149 context: dict[str, typing.Any] | None = None, 

150 status_code: int = 200, 

151 headers: typing.Mapping[str, str] | None = None, 

152 media_type: str | None = None, 

153 background: BackgroundTask | None = None, 

154 ) -> _TemplateResponse: 

155 # Deprecated usage 

156 ... 

157 

158 def TemplateResponse(self, *args: typing.Any, **kwargs: typing.Any) -> _TemplateResponse: 

159 if args: 

160 if isinstance(args[0], str): # the first argument is template name (old style) 

161 warnings.warn( 

162 "The `name` is not the first parameter anymore. " 

163 "The first parameter should be the `Request` instance.\n" 

164 'Replace `TemplateResponse(name, {"request": request})` by `TemplateResponse(request, name)`.', 

165 DeprecationWarning, 

166 ) 

167 

168 name = args[0] 

169 context = args[1] if len(args) > 1 else kwargs.get("context", {}) 

170 status_code = args[2] if len(args) > 2 else kwargs.get("status_code", 200) 

171 headers = args[2] if len(args) > 2 else kwargs.get("headers") 

172 media_type = args[3] if len(args) > 3 else kwargs.get("media_type") 

173 background = args[4] if len(args) > 4 else kwargs.get("background") 

174 

175 if "request" not in context: 

176 raise ValueError('context must include a "request" key') 

177 request = context["request"] 

178 else: # the first argument is a request instance (new style) 

179 request = args[0] 

180 name = args[1] if len(args) > 1 else kwargs["name"] 

181 context = args[2] if len(args) > 2 else kwargs.get("context", {}) 

182 status_code = args[3] if len(args) > 3 else kwargs.get("status_code", 200) 

183 headers = args[4] if len(args) > 4 else kwargs.get("headers") 

184 media_type = args[5] if len(args) > 5 else kwargs.get("media_type") 

185 background = args[6] if len(args) > 6 else kwargs.get("background") 

186 else: # all arguments are kwargs 

187 if "request" not in kwargs: 

188 warnings.warn( 

189 "The `TemplateResponse` now requires the `request` argument.\n" 

190 'Replace `TemplateResponse(name, {"context": context})` by `TemplateResponse(request, name)`.', 

191 DeprecationWarning, 

192 ) 

193 if "request" not in kwargs.get("context", {}): 

194 raise ValueError('context must include a "request" key') 

195 

196 context = kwargs.get("context", {}) 

197 request = kwargs.get("request", context.get("request")) 

198 name = typing.cast(str, kwargs["name"]) 

199 status_code = kwargs.get("status_code", 200) 

200 headers = kwargs.get("headers") 

201 media_type = kwargs.get("media_type") 

202 background = kwargs.get("background") 

203 

204 context.setdefault("request", request) 

205 for context_processor in self.context_processors: 

206 context.update(context_processor(request)) 

207 

208 template = self.get_template(name) 

209 return _TemplateResponse( 

210 template, 

211 context, 

212 status_code=status_code, 

213 headers=headers, 

214 media_type=media_type, 

215 background=background, 

216 )