No description
  • Python 96.7%
  • Just 3.3%
Find a file
Marcel Kröker 78fd09ef74
All checks were successful
Tests / tests-job (3.13) (push) Successful in 29s
Tests / tests-job (3.12) (push) Successful in 29s
Tests / tests-job (3.14) (push) Successful in 30s
wip
2026-06-03 11:19:42 +02:00
.github/workflows wip 2026-06-01 09:06:26 +02:00
jinja_typed_template wip 2026-06-03 11:19:42 +02:00
tests wip 2026-06-03 11:19:42 +02:00
.gitignore Initial commit 2026-06-01 08:16:21 +02:00
justfile justfile 2026-06-01 09:25:11 +02:00
LICENSE Initial commit 2026-06-01 08:16:21 +02:00
pyproject.toml fix flask rendering 2026-06-01 13:33:24 +02:00
README.md release 0.3.0 2026-06-01 10:32:39 +02:00
uv.lock fix flask rendering 2026-06-01 13:33:24 +02:00

jinja-typed-template

Type-safe Jinja2 templates with compile-time validation — keep your Python code and Jinja templates in sync, fail fast on mismatches, and never ship a broken template again.

Installation

pip install jinja-typed-template

For Flask support:

pip install jinja-typed-template[flask]

Quick Start

1. Annotate your template

Add a header comment declaring every variable the template uses, along with its type:

{#
    name: str
    age: int
    items: list[str]
#}
<h1>Hello {{ name }}!</h1>
<p>You are {{ age }} years old.</p>
<ul>
  {% for item in items %}
    <li>{{ item }}</li>
  {% endfor %}
</ul>

2. Define a typed template class

Subclass TypedTemplate, set the template name, and declare fields with type hints:

from jinja_typed_template import TypedTemplate, env_context
from jinja2 import Environment, FileSystemLoader

class ProfileTemplate(TypedTemplate):
    __template_name__ = "profile.html"
    name: str
    age: int
    items: list[str]

3. Render safely

Wrap usage in env_context to bind a Jinja2 environment, then instantiate and render:

env = Environment(loader=FileSystemLoader("templates"))

with env_context(env):
    t = ProfileTemplate(name="Alice", age=30, items=["apples", "bananas"])
    print(t.render())

4. Flask setup (optional)

If you're using Flask, initialize the extension on your app — then TypedTemplate works in any route without manual env_context:

from flask import Flask
from jinja_typed_template.flask import TypedTemplateExtension

app = Flask(__name__)
typed_templates = TypedTemplateExtension()
typed_templates.init_app(app)

Now use TypedTemplate directly in a route:

from jinja_typed_template import TypedTemplate

class ProfileTemplate(TypedTemplate):
    __template_name__ = "profile.html"
    name: str
    age: int

@app.route("/profile/<name>")
def profile(name: str):
    t = ProfileTemplate(name=name, age=30)
    return t.render()

What Gets Validated

Every TypedTemplate instance is validated at construction time (__post_init__). Three checks run automatically:

Check Catches
Type checking T(name=123) when name: str — raises TypeError
Variable matching Missing or extra fields vs. what the template actually uses — raises ValueError
Header comment consistency Header declares name: int but class says name: str — raises TypeError

This means you catch mismatches the moment you create the object, not when a user hits the page.

API Reference

TypedTemplate

Base class. Subclass it, set __template_name__, and declare typed fields.

Member Description
render() Render the template to a string
context_dict Dict of all field names → values
copy_updating(**kwargs) Return a new instance with some fields replaced (instances are frozen/immutable)

env_context(env)

Context manager that binds a Jinja2 Environment for the current thread/context. Required before creating or rendering any TypedTemplate.

from jinja_typed_template import env_context

with env_context(env):
    t = MyTemplate(...)

TypedTemplateExtension (Flask)

See the Flask setup in Quick Start above.

Header Comment Format

Place a Jinja2 comment at the very top of the template:

{#
    variable_name: TypeName
    another_var: list[str]
    optional_var: str = "default"
#}
  • variable_name must match a field on your TypedTemplate subclass.
  • TypeName uses a short-form representation (str, int, list[str], dict[str, int], etc.) and must match the Python type hint.
  • Default values after = are stripped — they're documentation only.

License

MIT