Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/starlette/templating.py: 39%
49 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:12 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:12 +0000
1import typing
2from os import PathLike
4from starlette.background import BackgroundTask
5from starlette.requests import Request
6from starlette.responses import Response
7from starlette.types import Receive, Scope, Send
9try:
10 import jinja2
12 # @contextfunction was renamed to @pass_context in Jinja 3.0, and was removed in 3.1
13 # hence we try to get pass_context (most installs will be >=3.1)
14 # and fall back to contextfunction,
15 # adding a type ignore for mypy to let us access an attribute that may not exist
16 if hasattr(jinja2, "pass_context"):
17 pass_context = jinja2.pass_context
18 else: # pragma: nocover
19 pass_context = jinja2.contextfunction # type: ignore[attr-defined]
20except ImportError: # pragma: nocover
21 jinja2 = None # type: ignore[assignment]
24class _TemplateResponse(Response):
25 media_type = "text/html"
27 def __init__(
28 self,
29 template: typing.Any,
30 context: dict,
31 status_code: int = 200,
32 headers: typing.Optional[typing.Mapping[str, str]] = None,
33 media_type: typing.Optional[str] = None,
34 background: typing.Optional[BackgroundTask] = None,
35 ):
36 self.template = template
37 self.context = context
38 content = template.render(context)
39 super().__init__(content, status_code, headers, media_type, background)
41 async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
42 request = self.context.get("request", {})
43 extensions = request.get("extensions", {})
44 if "http.response.debug" in extensions:
45 await send(
46 {
47 "type": "http.response.debug",
48 "info": {
49 "template": self.template,
50 "context": self.context,
51 },
52 }
53 )
54 await super().__call__(scope, receive, send)
57class Jinja2Templates:
58 """
59 templates = Jinja2Templates("templates")
61 return templates.TemplateResponse("index.html", {"request": request})
62 """
64 def __init__(
65 self,
66 directory: typing.Union[str, PathLike],
67 context_processors: typing.Optional[
68 typing.List[typing.Callable[[Request], typing.Dict[str, typing.Any]]]
69 ] = None,
70 **env_options: typing.Any
71 ) -> None:
72 assert jinja2 is not None, "jinja2 must be installed to use Jinja2Templates"
73 self.env = self._create_env(directory, **env_options)
74 self.context_processors = context_processors or []
76 def _create_env(
77 self, directory: typing.Union[str, PathLike], **env_options: typing.Any
78 ) -> "jinja2.Environment":
79 @pass_context
80 def url_for(context: dict, name: str, **path_params: typing.Any) -> str:
81 request = context["request"]
82 return request.url_for(name, **path_params)
84 loader = jinja2.FileSystemLoader(directory)
85 env_options.setdefault("loader", loader)
86 env_options.setdefault("autoescape", True)
88 env = jinja2.Environment(**env_options)
89 env.globals["url_for"] = url_for
90 return env
92 def get_template(self, name: str) -> "jinja2.Template":
93 return self.env.get_template(name)
95 def TemplateResponse(
96 self,
97 name: str,
98 context: dict,
99 status_code: int = 200,
100 headers: typing.Optional[typing.Mapping[str, str]] = None,
101 media_type: typing.Optional[str] = None,
102 background: typing.Optional[BackgroundTask] = None,
103 ) -> _TemplateResponse:
104 if "request" not in context:
105 raise ValueError('context must include a "request" key')
107 request = typing.cast(Request, context["request"])
108 for context_processor in self.context_processors:
109 context.update(context_processor(request))
111 template = self.get_template(name)
112 return _TemplateResponse(
113 template,
114 context,
115 status_code=status_code,
116 headers=headers,
117 media_type=media_type,
118 background=background,
119 )