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
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
1from __future__ import annotations
3import typing
4import warnings
5from os import PathLike
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
13try:
14 import jinja2
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]
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)
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)
59class Jinja2Templates:
60 """
61 templates = Jinja2Templates("templates")
63 return templates.TemplateResponse("index.html", {"request": request})
64 """
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: ...
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: ...
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
104 self._setup_env_defaults(self.env)
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)
115 return jinja2.Environment(**env_options)
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)
128 env.globals.setdefault("url_for", url_for)
130 def get_template(self, name: str) -> jinja2.Template:
131 return self.env.get_template(name)
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: ...
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 ...
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 )
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")
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')
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")
204 context.setdefault("request", request)
205 for context_processor in self.context_processors:
206 context.update(context_processor(request))
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 )