11from __future__ import annotations
22
3- import warnings
43from collections .abc import Callable , Mapping , Sequence
54from os import PathLike
6- from typing import Any , cast , overload
5+ from typing import TYPE_CHECKING , Any , overload
76
87from starlette .background import BackgroundTask
98from starlette .datastructures import URL
1817 # hence we try to get pass_context (most installs will be >=3.1)
1918 # and fall back to contextfunction,
2019 # adding a type ignore for mypy to let us access an attribute that may not exist
21- if hasattr ( jinja2 , "pass_context" ) :
20+ if TYPE_CHECKING :
2221 pass_context = jinja2 .pass_context
23- else : # pragma: no cover
24- pass_context = jinja2 .contextfunction # type: ignore[attr-defined]
25- except ModuleNotFoundError : # pragma: no cover
26- jinja2 = None # type: ignore[assignment]
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]
27+ except ImportError as _import_error : # pragma: no cover
28+ raise ImportError ("jinja2 must be installed to use Jinja2Templates" ) from _import_error
2729
2830
2931class _TemplateResponse (HTMLResponse ):
@@ -45,23 +47,22 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
4547 request = self .context .get ("request" , {})
4648 extensions = request .get ("extensions" , {})
4749 if "http.response.debug" in extensions : # pragma: no branch
48- await send (
49- {
50- "type" : "http.response.debug" ,
51- "info" : {
52- "template" : self .template ,
53- "context" : self .context ,
54- },
55- }
56- )
50+ await send ({"type" : "http.response.debug" , "info" : {"template" : self .template , "context" : self .context }})
5751 await super ().__call__ (scope , receive , send )
5852
5953
6054class Jinja2Templates :
61- """
62- templates = Jinja2Templates("templates")
55+ """Jinja2 template renderer.
56+
57+ Example:
58+ ```python
59+ from starlette.templating import Jinja2Templates
60+
61+ templates = Jinja2Templates(directory="templates")
6362
64- return templates.TemplateResponse("index.html", {"request": request})
63+ async def homepage(request: Request) -> Response:
64+ return templates.TemplateResponse(request, "index.html")
65+ ```
6566 """
6667
6768 @overload
@@ -70,7 +71,6 @@ def __init__(
7071 directory : str | PathLike [str ] | Sequence [str | PathLike [str ]],
7172 * ,
7273 context_processors : list [Callable [[Request ], dict [str , Any ]]] | None = None ,
73- ** env_options : Any ,
7474 ) -> None : ...
7575
7676 @overload
@@ -87,34 +87,17 @@ def __init__(
8787 * ,
8888 context_processors : list [Callable [[Request ], dict [str , Any ]]] | None = None ,
8989 env : jinja2 .Environment | None = None ,
90- ** env_options : Any ,
9190 ) -> None :
92- if env_options :
93- warnings .warn (
94- "Extra environment options are deprecated. Use a preconfigured jinja2.Environment instead." ,
95- DeprecationWarning ,
96- )
97- assert jinja2 is not None , "jinja2 must be installed to use Jinja2Templates"
9891 assert bool (directory ) ^ bool (env ), "either 'directory' or 'env' arguments must be passed"
9992 self .context_processors = context_processors or []
10093 if directory is not None :
101- self .env = self ._create_env (directory , ** env_options )
94+ loader = jinja2 .FileSystemLoader (directory )
95+ self .env = jinja2 .Environment (loader = loader )
10296 elif env is not None : # pragma: no branch
10397 self .env = env
10498
10599 self ._setup_env_defaults (self .env )
106100
107- def _create_env (
108- self ,
109- directory : str | PathLike [str ] | Sequence [str | PathLike [str ]],
110- ** env_options : Any ,
111- ) -> jinja2 .Environment :
112- loader = jinja2 .FileSystemLoader (directory )
113- env_options .setdefault ("loader" , loader )
114- env_options .setdefault ("autoescape" , True )
115-
116- return jinja2 .Environment (** env_options )
117-
118101 def _setup_env_defaults (self , env : jinja2 .Environment ) -> None :
119102 @pass_context
120103 def url_for (
@@ -131,7 +114,6 @@ def url_for(
131114 def get_template (self , name : str ) -> jinja2 .Template :
132115 return self .env .get_template (name )
133116
134- @overload
135117 def TemplateResponse (
136118 self ,
137119 request : Request ,
@@ -141,66 +123,23 @@ def TemplateResponse(
141123 headers : Mapping [str , str ] | None = None ,
142124 media_type : str | None = None ,
143125 background : BackgroundTask | None = None ,
144- ) -> _TemplateResponse : ...
145-
146- @overload
147- def TemplateResponse (
148- self ,
149- name : str ,
150- context : dict [str , Any ] | None = None ,
151- status_code : int = 200 ,
152- headers : Mapping [str , str ] | None = None ,
153- media_type : str | None = None ,
154- background : BackgroundTask | None = None ,
155126 ) -> _TemplateResponse :
156- # Deprecated usage
157- ...
158-
159- def TemplateResponse (self , * args : Any , ** kwargs : Any ) -> _TemplateResponse :
160- if args :
161- if isinstance (args [0 ], str ): # the first argument is template name (old style)
162- warnings .warn (
163- "The `name` is not the first parameter anymore. "
164- "The first parameter should be the `Request` instance.\n "
165- 'Replace `TemplateResponse(name, {"request": request})` by `TemplateResponse(request, name)`.' ,
166- DeprecationWarning ,
167- )
168-
169- name = args [0 ]
170- context = args [1 ] if len (args ) > 1 else kwargs .get ("context" , {})
171- status_code = args [2 ] if len (args ) > 2 else kwargs .get ("status_code" , 200 )
172- headers = args [3 ] if len (args ) > 3 else kwargs .get ("headers" )
173- media_type = args [4 ] if len (args ) > 4 else kwargs .get ("media_type" )
174- background = args [5 ] if len (args ) > 5 else kwargs .get ("background" )
175-
176- if "request" not in context :
177- raise ValueError ('context must include a "request" key' )
178- request = context ["request" ]
179- else : # the first argument is a request instance (new style)
180- request = args [0 ]
181- name = args [1 ] if len (args ) > 1 else kwargs ["name" ]
182- context = args [2 ] if len (args ) > 2 else kwargs .get ("context" , {})
183- status_code = args [3 ] if len (args ) > 3 else kwargs .get ("status_code" , 200 )
184- headers = args [4 ] if len (args ) > 4 else kwargs .get ("headers" )
185- media_type = args [5 ] if len (args ) > 5 else kwargs .get ("media_type" )
186- background = args [6 ] if len (args ) > 6 else kwargs .get ("background" )
187- else : # all arguments are kwargs
188- if "request" not in kwargs :
189- warnings .warn (
190- "The `TemplateResponse` now requires the `request` argument.\n "
191- 'Replace `TemplateResponse(name, {"context": context})` by `TemplateResponse(request, name)`.' ,
192- DeprecationWarning ,
193- )
194- if "request" not in kwargs .get ("context" , {}):
195- raise ValueError ('context must include a "request" key' )
196-
197- context = kwargs .get ("context" , {})
198- request = kwargs .get ("request" , context .get ("request" ))
199- name = cast (str , kwargs ["name" ])
200- status_code = kwargs .get ("status_code" , 200 )
201- headers = kwargs .get ("headers" )
202- media_type = kwargs .get ("media_type" )
203- background = kwargs .get ("background" )
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 {}
204143
205144 context .setdefault ("request" , request )
206145 for context_processor in self .context_processors :
0 commit comments