diff --git a/README.md b/README.md
index a293b332..bfdcce6c 100644
--- a/README.md
+++ b/README.md
@@ -80,10 +80,11 @@ Starlette does not have any hard dependencies, but the following are optional:
* [`requests`][requests] - Required if you want to use the `TestClient`.
* [`aiofiles`][aiofiles] - Required if you want to use `FileResponse` or `StaticFiles`.
+* [`jinja2`][jinja2] - Required if you want to use the default template configuration.
* [`python-multipart`][python-multipart] - Required if you want to support form parsing, with `request.form()`.
-* [`graphene`][graphene] - Required for `GraphQLApp` support.
* [`itsdangerous`][itsdangerous] - Required for `SessionMiddleware` support.
* [`pyyaml`][pyyaml] - Required for `SchemaGenerator` support.
+* [`graphene`][graphene] - Required for `GraphQLApp` support.
* [`ujson`][ujson] - Required if you want to use `UJSONResponse`.
You can install all of these with `pip3 install starlette[full]`.
diff --git a/docs/index.md b/docs/index.md
index 1cdb5df0..362f5b39 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -79,10 +79,11 @@ Starlette does not have any hard dependencies, but the following are optional:
* [`requests`][requests] - Required if you want to use the `TestClient`.
* [`aiofiles`][aiofiles] - Required if you want to use `FileResponse` or `StaticFiles`.
+* [`jinja2`][jinja2] - Required if you want to use the default template configuration.
* [`python-multipart`][python-multipart] - Required if you want to support form parsing, with `request.form()`.
-* [`graphene`][graphene] - Required for `GraphQLApp` support.
* [`itsdangerous`][itsdangerous] - Required for `SessionMiddleware` support.
* [`pyyaml`][pyyaml] - Required for `SchemaGenerator` support.
+* [`graphene`][graphene] - Required for `GraphQLApp` support.
* [`ujson`][ujson] - Required if you want to use `UJSONResponse`.
You can install all of these with `pip3 install starlette[full]`.
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 7c123974..cbedd51a 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,3 +1,22 @@
+## 0.8.1
+
+## Templating
+
+* Add a default templating configuration with Jinja2.
+
+Allows the following:
+
+```python
+app = Starlette(template_directory="templates")
+
+@app.route('/')
+async def homepage(request):
+ # `url_for` is available inside the template.
+ template = app.get_template('index.html')
+ content = template.render(request=request)
+ return HTMLResponse(content)
+```
+
## 0.8.0
### Exceptions
diff --git a/docs/templates.md b/docs/templates.md
index 9cb22f45..f4e59c44 100644
--- a/docs/templates.md
+++ b/docs/templates.md
@@ -1,8 +1,34 @@
-Starlette is not coupled to any particular templating engine, but Jinja2
-provides an excellent choice.
+Starlette is not *strictly* coupled to any particular templating engine, but
+Jinja2 provides an excellent choice.
-Here we're going to take a look at a complete example of how you can configure
-a Jinja2 environment together with Starlette.
+The `Starlette` application class provides a simple way to get `jinja2`
+configured. This is probably what you want to use by default.
+
+```python
+app = Starlette(debug=True, template_directory='templates')
+app.mount('/static', StaticFiles(directory='statics'), name='static')
+
+
+@app.route('/')
+async def homepage(request):
+ template = app.get_template('index.html')
+ content = template.render(request=request)
+ return HTMLResponse(content)
+```
+
+If you include `request` in the template context, then the `url_for` function
+will also be available within your template code.
+
+The Jinja2 `Environment` instance is available as `app.template_env`.
+
+## Handling templates explicitly
+
+If you don't want to use `jinja2`, or you don't want to rely on
+Starlette's default configuration you can configure a template renderer
+explicitly instead.
+
+Here we're going to take a look at an example of how you can explicitly
+configure a Jinja2 environment together with Starlette.
```python
from starlette.applications import Starlette
@@ -34,6 +60,9 @@ async def homepage(request):
return HTMLResponse(content)
```
+This gives you the equivalent of the default `app.get_template()`, but we've
+got all the configuration explicitly out in the open now.
+
The important parts to note from the above example are:
* The StaticFiles app has been mounted with `name='static'`, meaning we can use `app.url_path_for('static', path=...)` or `request.url_for('static', path=...)`.
diff --git a/requirements.txt b/requirements.txt
index e135e521..10d91790 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,6 +2,7 @@
aiofiles
graphene
itsdangerous
+jinja2
python-multipart
pyyaml
requests
diff --git a/starlette/__init__.py b/starlette/__init__.py
index 777f190d..8088f751 100644
--- a/starlette/__init__.py
+++ b/starlette/__init__.py
@@ -1 +1 @@
-__version__ = "0.8.0"
+__version__ = "0.8.1"
diff --git a/starlette/applications.py b/starlette/applications.py
index db47ef62..560a60eb 100644
--- a/starlette/applications.py
+++ b/starlette/applications.py
@@ -11,7 +11,7 @@ from starlette.types import ASGIApp, ASGIInstance, Scope
class Starlette:
- def __init__(self, debug: bool = False) -> None:
+ def __init__(self, debug: bool = False, template_directory: str = None) -> None:
self._debug = debug
self.router = Router()
self.lifespan_handler = LifespanHandler()
@@ -20,6 +20,7 @@ class Starlette:
self.exception_middleware, debug=debug
)
self.schema_generator = None # type: typing.Optional[BaseSchemaGenerator]
+ self.template_env = self.load_template_env(template_directory)
@property
def routes(self) -> typing.List[BaseRoute]:
@@ -35,6 +36,26 @@ class Starlette:
self.exception_middleware.debug = value
self.error_middleware.debug = value
+ def load_template_env(self, template_directory: str = None) -> typing.Any:
+ if template_directory is None:
+ return None
+
+ # Import jinja2 lazily.
+ import jinja2
+
+ @jinja2.contextfunction
+ def url_for(context: dict, name: str, **path_params: typing.Any) -> str:
+ request = context["request"]
+ return request.url_for(name, **path_params)
+
+ loader = jinja2.FileSystemLoader(str(template_directory))
+ env = jinja2.Environment(loader=loader, autoescape=True)
+ env.globals["url_for"] = url_for
+ return env
+
+ def get_template(self, name: str) -> typing.Any:
+ return self.template_env.get_template(name)
+
@property
def schema(self) -> dict:
assert self.schema_generator is not None
diff --git a/tests/test_templates.py b/tests/test_templates.py
new file mode 100644
index 00000000..bc213a0b
--- /dev/null
+++ b/tests/test_templates.py
@@ -0,0 +1,23 @@
+import os
+
+from starlette.applications import Starlette
+from starlette.responses import HTMLResponse
+from starlette.testclient import TestClient
+
+
+def test_templates(tmpdir):
+ path = os.path.join(tmpdir, "index.html")
+ with open(path, "w") as file:
+ file.write("Hello, world")
+
+ app = Starlette(debug=True, template_directory=tmpdir)
+
+ @app.route("/")
+ async def homepage(request):
+ template = app.get_template("index.html")
+ content = template.render(request=request)
+ return HTMLResponse(content)
+
+ client = TestClient(app)
+ response = client.get("/")
+ assert response.text == "Hello, world"