diff --git a/docs/templates.md b/docs/templates.md index 749c7a8c2..66f2f9ca1 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -1,6 +1,14 @@ Starlette is not _strictly_ coupled to any particular templating engine, but Jinja2 provides an excellent choice. +### Jinja2Templates + +Signature: `Jinja2Templates(directory, context_processors=None, **env_options)` + +* `directory` - A string, [os.Pathlike][pathlike] or a list of strings or [os.Pathlike][pathlike] denoting a directory path. +* `context_processors` - A list of functions that return a dictionary to add to the template context. +* `**env_options` - Additional keyword arguments to pass to the Jinja2 environment. + Starlette provides a simple way to get `jinja2` configured. This is probably what you want to use by default. @@ -128,3 +136,4 @@ for example, strictly evaluate any database queries within the view and include the final results in the context. [jinja2]: https://jinja.palletsprojects.com/en/3.0.x/api/?highlight=environment#writing-filters +[pathlike]: https://docs.python.org/3/library/os.html#os.PathLike diff --git a/starlette/templating.py b/starlette/templating.py index 7df8f6d29..f5da207bd 100644 --- a/starlette/templating.py +++ b/starlette/templating.py @@ -64,7 +64,9 @@ class Jinja2Templates: def __init__( self, - directory: typing.Union[str, PathLike], + directory: typing.Union[ + str, PathLike, typing.Sequence[typing.Union[str, PathLike]] + ], context_processors: typing.Optional[ typing.List[typing.Callable[[Request], typing.Dict[str, typing.Any]]] ] = None, @@ -75,7 +77,11 @@ def __init__( self.context_processors = context_processors or [] def _create_env( - self, directory: typing.Union[str, PathLike], **env_options: typing.Any + self, + directory: typing.Union[ + str, PathLike, typing.Sequence[typing.Union[str, PathLike]] + ], + **env_options: typing.Any, ) -> "jinja2.Environment": @pass_context def url_for(context: dict, name: str, **path_params: typing.Any) -> URL: diff --git a/tests/test_templates.py b/tests/test_templates.py index 2d918f7d0..527872666 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -1,4 +1,5 @@ import os +from pathlib import Path import pytest @@ -88,3 +89,38 @@ async def dispatch(self, request, call_next): assert response.text == "Hello, world" assert response.template.name == "index.html" assert set(response.context.keys()) == {"request"} + + +def test_templates_with_directories(tmp_path: Path, test_client_factory): + dir_a = tmp_path.resolve() / "a" + dir_a.mkdir() + template_a = dir_a / "template_a.html" + template_a.write_text(" a") + + async def page_a(request): + return templates.TemplateResponse("template_a.html", {"request": request}) + + dir_b = tmp_path.resolve() / "b" + dir_b.mkdir() + template_b = dir_b / "template_b.html" + template_b.write_text(" b") + + async def page_b(request): + return templates.TemplateResponse("template_b.html", {"request": request}) + + app = Starlette( + debug=True, + routes=[Route("/a", endpoint=page_a), Route("/b", endpoint=page_b)], + ) + templates = Jinja2Templates(directory=[dir_a, dir_b]) + + client = test_client_factory(app) + response = client.get("/a") + assert response.text == " a" + assert response.template.name == "template_a.html" + assert set(response.context.keys()) == {"request"} + + response = client.get("/b") + assert response.text == " b" + assert response.template.name == "template_b.html" + assert set(response.context.keys()) == {"request"}