Metadata-Version: 2.4
Name: vcti-template
Version: 1.1.1
Summary: Jinja2-based template rendering engine with streaming support
Author: Visual Collaboration Technologies Inc.
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: jinja2
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-cov; extra == "test"
Provides-Extra: lint
Requires-Dist: ruff; extra == "lint"
Dynamic: license-file

# Template

Jinja2-based template rendering engine with streaming support for Python.

## Purpose

VCollab applications render data into various output formats — HTML reports,
chart visualizations, CSV exports, styled tables. The rendering logic is
always the same: load a template, pass data, produce output.

`vcti-template` provides a lightweight engine for this pattern with three
template sources (registry, filesystem, inline), streaming support for
large datasets, and a clean separation between template management and
domain-specific data preparation.

## Installation

```bash
pip install vcti-template

# Specific version
pip install vcti-template@v1.1.0
```

From a GitHub release wheel:

```bash
pip install vcti-template>=1.1.1
```

## Quick Start

### Registry + Manager

```python
from vcti.template import TemplateManager, TemplateRegistry

# Register named templates
registry = TemplateRegistry()
registry.register("greeting", "Hello, {{ name }}!")
registry.register("row", "<tr><td>{{ value }}</td></tr>")

# Create manager with the registry
tm = TemplateManager(registry=registry)

# Render a template
tm.render("greeting", name="World")  # "Hello, World!"
```

### Filesystem Templates

```python
from vcti.template import TemplateManager

# Load templates from a directory
tm = TemplateManager(template_dirs=["./templates"])

# Render a file template
tm.render("report.html.jinja2", title="Q1 Report", data=summary)
```

### Inline Rendering

```python
from vcti.template import TemplateManager

tm = TemplateManager()
tm.render_string("{{ x }} + {{ y }} = {{ x + y }}", x=2, y=3)
# "2 + 3 = 5"
```

### Streaming for Large Data

```python
from vcti.template import TemplateManager, TemplateRegistry

registry = TemplateRegistry()
registry.register("header", "<table><tr><th>Name</th></tr>")
registry.register("row", "<tr><td>{{ name }}</td></tr>")
registry.register("footer", "</table>")

tm = TemplateManager(registry=registry)

# Render one chunk at a time — memory stays bounded
chunks = [{"name": row.name} for row in large_dataset.iter_rows()]

with open("output.html", "w") as f:
    for fragment in tm.render_streaming(
        "row",
        chunks,
        header_template="header",
        header_context={},
        footer_template="footer",
        footer_context={},
    ):
        f.write(fragment)
```

### Strict Mode

```python
from vcti.template import TemplateManager, TemplateRenderError

# Catch undefined variables instead of rendering empty strings
tm = TemplateManager(strict=True)

tm.render_string("Hello, {{ name }}!", name="World")  # OK

try:
    tm.render_string("Hello, {{ name }}!")  # no name provided
except TemplateRenderError as exc:
    print(exc)  # 'name' is undefined
```

### Custom Filters and Globals

```python
from vcti.template import TemplateManager

tm = TemplateManager(
    filters={"shout": lambda s: s.upper() + "!!!"},
    globals={"app_name": "MyApp"},
)

tm.render_string("{{ app_name }}: {{ msg|shout }}", msg="hello")
# "MyApp: HELLO!!!"
```

## Error Handling

All template-specific errors inherit from `TemplateError`:

```python
from vcti.template import TemplateError, TemplateNotFoundError

try:
    tm.render("missing_template")
except TemplateNotFoundError:
    ...  # handle specifically
except TemplateError:
    ...  # catch-all for any template issue
```

| Exception | When |
|-----------|------|
| `TemplateNotFoundError` | Name not in registry or filesystem |
| `TemplateSyntaxError` | Invalid Jinja2 syntax |
| `TemplateRenderError` | Render failure (e.g., undefined var in strict mode) |

## API Summary

### TemplateRegistry

| Method | Description |
|--------|-------------|
| `register(name, template, *, replace=False)` | Register a template string |
| `get(name)` | Retrieve a template string |
| `has(name)` | Check if name is registered |
| `remove(name)` | Remove a template |
| `names()` | List all registered names |
| `templates` | Immutable view of all templates |

### TemplateManager

| Method | Description |
|--------|-------------|
| `render(name, **context)` | Render a named template |
| `render_string(template, **context)` | Render an inline template string |
| `render_streaming(name, chunks, ...)` | Yield rendered fragments per chunk |

| Constructor Option | Default | Description |
|-------------------|---------|-------------|
| `registry` | `None` | Template registry instance |
| `template_dirs` | `None` | Filesystem directories to search |
| `autoescape_extensions` | `["html", "xml"]` | Extensions to autoescape |
| `strict` | `False` | Raise on undefined variables |
| `filters` | `None` | Custom Jinja2 filters |
| `globals` | `None` | Variables available in all templates |

Resolution order for `render()`: registry → filesystem → error.

## Dependencies

- `jinja2` — template engine
