Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/starlette/templating.py: 46%
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
3from collections.abc import Callable, Mapping, Sequence
4from os import PathLike
5from typing import TYPE_CHECKING, Any, overload
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 TYPE_CHECKING:
21 pass_context = jinja2.pass_context
22 else:
23 if hasattr(jinja2, "pass_context"):
24 pass_context = jinja2.pass_context
25 else: # pragma: no cover
26 pass_context = jinja2.contextfunction # type: ignore[attr-defined]
27except ImportError as _import_error: # pragma: no cover
28 raise ImportError("jinja2 must be installed to use Jinja2Templates") from _import_error
31class _TemplateResponse(HTMLResponse):
32 def __init__(
33 self,
34 template: Any,
35 context: dict[str, Any],
36 status_code: int = 200,
37 headers: Mapping[str, str] | None = None,
38 media_type: str | None = None,
39 background: BackgroundTask | None = None,
40 ):
41 self.template = template
42 self.context = context
43 content = template.render(context)
44 super().__init__(content, status_code, headers, media_type, background)
46 async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
47 request = self.context.get("request", {})
48 extensions = request.get("extensions", {})
49 if "http.response.debug" in extensions: # pragma: no branch
50 await send({"type": "http.response.debug", "info": {"template": self.template, "context": self.context}})
51 await super().__call__(scope, receive, send)
54class Jinja2Templates:
55 """Jinja2 template renderer.
57 Example:
58 ```python
59 from starlette.templating import Jinja2Templates
61 templates = Jinja2Templates(directory="templates")
63 async def homepage(request: Request) -> Response:
64 return templates.TemplateResponse(request, "index.html")
65 ```
66 """
68 @overload
69 def __init__(
70 self,
71 directory: str | PathLike[str] | Sequence[str | PathLike[str]],
72 *,
73 context_processors: list[Callable[[Request], dict[str, Any]]] | None = None,
74 ) -> None: ...
76 @overload
77 def __init__(
78 self,
79 *,
80 env: jinja2.Environment,
81 context_processors: list[Callable[[Request], dict[str, Any]]] | None = None,
82 ) -> None: ...
84 def __init__(
85 self,
86 directory: str | PathLike[str] | Sequence[str | PathLike[str]] | None = None,
87 *,
88 context_processors: list[Callable[[Request], dict[str, Any]]] | None = None,
89 env: jinja2.Environment | None = None,
90 ) -> None:
91 assert bool(directory) ^ bool(env), "either 'directory' or 'env' arguments must be passed"
92 self.context_processors = context_processors or []
93 if directory is not None:
94 loader = jinja2.FileSystemLoader(directory)
95 self.env = jinja2.Environment(loader=loader, autoescape=jinja2.select_autoescape())
96 elif env is not None: # pragma: no branch
97 self.env = env
99 self._setup_env_defaults(self.env)
101 def _setup_env_defaults(self, env: jinja2.Environment) -> None:
102 @pass_context
103 def url_for(
104 context: dict[str, Any],
105 name: str,
106 /,
107 **path_params: Any,
108 ) -> URL:
109 request: Request = context["request"]
110 return request.url_for(name, **path_params)
112 env.globals.setdefault("url_for", url_for)
114 def get_template(self, name: str) -> jinja2.Template:
115 return self.env.get_template(name)
117 def TemplateResponse(
118 self,
119 request: Request,
120 name: str,
121 context: dict[str, Any] | None = None,
122 status_code: int = 200,
123 headers: Mapping[str, str] | None = None,
124 media_type: str | None = None,
125 background: BackgroundTask | None = None,
126 ) -> _TemplateResponse:
127 """
128 Render a template and return an HTML response.
130 Args:
131 request: The incoming request instance.
132 name: The template file name to render.
133 context: Variables to pass to the template.
134 status_code: HTTP status code for the response.
135 headers: Additional headers to include in the response.
136 media_type: Media type for the response.
137 background: Background task to run after response is sent.
139 Returns:
140 An HTML response with the rendered template content.
141 """
142 context = context or {}
144 context.setdefault("request", request)
145 for context_processor in self.context_processors:
146 context.update(context_processor(request))
148 template = self.get_template(name)
149 return _TemplateResponse(
150 template,
151 context,
152 status_code=status_code,
153 headers=headers,
154 media_type=media_type,
155 background=background,
156 )