Skip to content

HTML Templating

What is templating?

If you're building any sort of website, you likely don't want to write HTML from Python strings. Instead, you would rather just render HTML files and keep your Python code seperate.

However, this has a drawback: you can't put variables into your HTML. Nearly all Python web frameworks use templating as a solution.

Templating is the use of a template engine to put Python code in your HTML. For a more in-depth explanation, see the Python Wiki.

Templating API

In View, the main template API entry point is the template function. Because this function performs I/O, it is asynchronous.

The only required argument to template is the name or path of the template to be used. For example:

from view import new_app, template

app = new_app()

@app.get("/")
async def index():
    return await template("index")  # this refers to index.html

@app.get("/other")
async def other():
    return await template("index.html")  # works the same way

app.run()

The most notable difference about view.py's templating API is that parameters are automatically included from your scope (i.e. you don't have to pass them into the call to template). If you're against this behavior, you may disable it in the configuration via the globals and locals settings.

view.templates.template(name: str | Path, directory: str | Path | None = _ConfigSpecified, engine: TemplateEngine | None = _ConfigSpecified, frame: Frame | None | _CurrentFrameType = _CurrentFrame, app: App | None = None, **parameters: Any) -> HTML async

Render a template with the specified engine. This returns a view.py HTML response.

Source code in src/view/templates.py
async def template(
    name: str | Path,
    directory: str | Path | None = _ConfigSpecified,
    engine: TemplateEngine | None = _ConfigSpecified,
    frame: Frame | None | _CurrentFrameType = _CurrentFrame,
    app: App | None = None,
    **parameters: Any,
) -> HTML:
    """Render a template with the specified engine. This returns a view.py HTML response."""
    from .app import get_app

    try:
        conf = app.config.templates if app else get_app().config.templates
    except BadEnvironmentError:
        conf = _DEFAULT_CONF

    directory = Path(directory or conf.directory)
    engine = engine or conf.engine

    if isinstance(name, str):
        if not name.endswith(".html"):
            name += ".html"

        name = Path(name)

    path = directory / name
    params: dict[str, Any] = {}
    if frame:
        if frame is _CurrentFrame:
            frame = inspect.currentframe()
            assert frame, "failed to get frame"
            while frame.f_code.co_filename == __file__:
                frame = frame.f_back
                assert frame, "frame has no f_back"

        assert isinstance(frame, Frame)

        if conf.globals:
            params.update(frame.f_globals)

        if conf.locals:
            params.update(frame.f_locals)

    params.update(parameters)

    async with aiofiles.open(path) as f:
        source = await f.read()

    return HTML(await render(source, engine, params, app=app))

You can override the template engine and settings via the engine and directory parameters. For example, if the engine was view, the below would use mako:

from view import new_app, template

app = new_app()

@app.get('/')
async def index():
    return await template("index", engine="mako")

app.run()

There's also a direct variation of template on App.

from view import new_app

app = new_app()

@app.get("/")
async def index():
    return await app.template("index")

app.run()

The following template engines are supported:

The View Engine

View has it's own built in template engine that is used by default. It's based around the usage of a <view> tag, which is more limited, yet pretty to look at.

A <view> element can have any of the following attributes:

  • ref: Can be any Python expression (including variable references).
  • template: Loads another template in place.
  • if: Shows the element if the expression is truthy.
  • elif: Shows the element if the expression is truthy and if the previous if or elif was falsy.
  • else: Shows the element if all the previous if and elif's were falsy.
  • iter: May be any iterable expression. An item attribute must be present if this attribute is set.
  • item: Specifies the name for the item in each iteration. Always present when iter is set.

Examples

ref can be used to take variables, but may also be used to display any Python expression. For example, if you had defined hello = "world":

<p>Hello, <view ref="hello" /></p>
<p>The length of hello is <view ref="len(hello)" /></p>

If you had declared my_list = [1, 2, 3], you could iterate through it like so:

<view iter="my_list" item="i">
    <view ref="i" />
</view>
The above would result in 123

if, elif, and else are only shown if their cases are met. So, for example:

<view if="user.type == 'admin'">
    <view template="admin_panel" />
</view>
<view elif="user.type == 'moderator'">
    <view template="mod_panel" />
</view>
<view else>
    <p>You must be an admin to use the admin panel!</p>
</view>

Using Other Engines

If you would like to use an unsupported engine (or use extra features of a supported engine), you can do one of two things:

  • Make a feature request on GitHub requesting for support.
  • Manually use it's API to return a response from a route.

For example, if you wanted to customize Jinja, you shouldn't use View's template, but instead just use it manually:

from view import new_app
from jinja2 import Environment

app = new_app()
env = Environment()

@app.get('/')
async def index():
    return env.get_template("mytemplate.html").render()

app.run()

Review

Template engines are used to mix your Python code and HTML. You can use View's template or (App.template, if the App is available already) function to render a template with one of the supported engines, which are:

If you would like to use an unsupported engine, you can make a feature request on GitHub, or use it's API manually.