Metadata-Version: 2.3
Name: jinja-typed-template
Version: 0.6.1
Summary: Static type checking for Jinja templates
Author: Marcel Kröker
Author-email: Marcel Kröker <kroeker.marcel@gmail.com>
License: MIT License
         
         Copyright (c) 2026 mkrd
         
         Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
         associated documentation files (the "Software"), to deal in the Software without restriction, including
         without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
         following conditions:
         
         The above copyright notice and this permission notice shall be included in all copies or substantial
         portions of the Software.
         
         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
         LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
         EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
         IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
         USE OR OTHER DEALINGS IN THE SOFTWARE.
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Dist: jinja2>=3.1.6
Requires-Dist: flask>=3.0 ; extra == 'flask'
Requires-Python: >=3.12, <3.15
Provides-Extra: flask
Description-Content-Type: text/markdown

# 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

```bash
pip install jinja-typed-template
```

For Flask support:

```bash
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:

```jinja
{#
    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:

```python
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:

```python
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`:

```python
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:

```python
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`.

```python
from jinja_typed_template import env_context

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

### `TypedTemplateExtension` (Flask)

See the [Flask setup](#4-flask-setup-optional) in Quick Start above.

## Header Comment Format

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

```jinja
{#
    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
