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

56 statements  

1from __future__ import annotations 

2 

3from collections.abc import Callable, Mapping, Sequence 

4from os import PathLike 

5from typing import TYPE_CHECKING, Any, overload 

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 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 

29 

30 

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) 

45 

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) 

52 

53 

54class Jinja2Templates: 

55 """Jinja2 template renderer. 

56 

57 Example: 

58 ```python 

59 from starlette.templating import Jinja2Templates 

60 

61 templates = Jinja2Templates(directory="templates") 

62 

63 async def homepage(request: Request) -> Response: 

64 return templates.TemplateResponse(request, "index.html") 

65 ``` 

66 """ 

67 

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: ... 

75 

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: ... 

83 

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 

98 

99 self._setup_env_defaults(self.env) 

100 

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) 

111 

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

113 

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

115 return self.env.get_template(name) 

116 

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. 

129 

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. 

138 

139 Returns: 

140 An HTML response with the rendered template content. 

141 """ 

142 context = context or {} 

143 

144 context.setdefault("request", request) 

145 for context_processor in self.context_processors: 

146 context.update(context_processor(request)) 

147 

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 )