Templating
Nexios provides a powerful templating system built on top of Jinja2, offering features like template inheritance, context management, custom filters, and more.
Quick Start
from nexios import NexiosApp
from nexios.templating import render,TemplateEngine
app = NexiosApp()
engine = TemplateEngine()
engine.setup_environment()
@app.get("/")
async def home(request, response):
return await render("home.html", {"title": "Welcome"}, request=request)
Tip
Without setting up the templating engine , the render function throws a Notimpemented error
Customizing Default Configuration
There are several ways to customize the templating system:
1. Using setup_environment
The simplest way is to set template options in your app configuration:
from nexios.templating import TemplateConfig
template_config = TemplateConfig(
template_dir = "templates"
)
engine.setup_environment(template_config)
2. Using App Config
You can also nexios app config optionally
from nexios import NexiosApp,MakeConfig
from nexios.templating import TemplateConfig
config = MakeConfig(
{
"templating" : TemplateConfig(
template_dir = "templates"
)
}
)
app = NexiosApp(config = config)
3. Runtime Configuration Updates
You can update configuration at runtime:
from nexios.templating import TemplateEngine
from pathlib import Path
engine = TemplateEngine()
engine.setup_environment()
engine.env.filters["custom"] = my_custom_filter
# Add new globals
engine.env.globals.update({
"api_version": "2.0",
"debug": True
})
engine.config.template_dir = Path("new_templates")
engine._setup_environment()
Configuration Options
The TemplateConfig
class supports the following options:
Option | Type | Default | Description |
---|---|---|---|
template_dir | str/Path | "templates" | Template directory path |
cache_size | int | 100 | Maximum templates to cache |
auto_reload | bool | True | Reload changed templates |
encoding | str | "utf-8" | Template file encoding |
enable_async | bool | True | Enable async rendering |
trim_blocks | bool | True | Strip first newline after block |
lstrip_blocks | bool | True | Strip leading spaces and tabs |
custom_filters | Dict[str, callable] | {} | Custom template filters |
custom_globals | Dict[str, Any] | {} | Global template variables |
Template Inheritance
Base template (base.html
):
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<nav>{% block nav %}{% endblock %}</nav>
<main>{% block content %}{% endblock %}</main>
<footer>{% block footer %}{% endblock %}</footer>
</body>
</html>
Child template:
{% extends "base.html" %}
{% block title %}Welcome{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
{{ content }}
{% endblock %}
Context Middleware
Add global and request-specific context to your templates:
from nexios.templating.middleware import template_context
async def user_context(request):
return {
"user": await get_current_user(request),
"messages": await get_flash_messages(request)
}
app.add_middleware(template_context(
default_context={
"version": "1.0.0",
"nav_links": [...]
},
context_processor=user_context
))
Utility Functions
The templating system includes several utility functions:
from nexios.templating.utils import (
truncate,
format_datetime,
static_hash,
merge_dicts
)
# Truncate text
{{ long_text|truncate(100) }}
# Format dates
{{ date|format_datetime("%Y-%m-%d") }}
# Cache busting for static files
{{ static_url('style.css') }}?v={{ static_hash('static/style.css') }}
Best Practices
Template Organization
- Keep templates in a dedicated directory
- Use meaningful names and subdirectories
- Follow a consistent naming convention
Context Management
- Use middleware for global context
- Keep context processors focused and lightweight
- Cache expensive context operations
Performance
- Enable template caching in production
- Use async rendering for I/O operations
- Minimize template complexity
Security
- HTML escaping is enabled by default
- Use
|safe
filter carefully - Validate user input before rendering
API Reference
Render Function
async def render(
template_name: str,
context: Dict[str, Any] = None,
status_code: int = 200,
headers: Dict[str, str] = None,
request: Request = None,
**kwargs
) -> Response
Template Context Middleware
def template_context(
default_context: Optional[Dict[str, Any]] = None,
context_processor: Optional[
Callable[[Request], Awaitable[Dict[str, Any]]]
] = None
) -> TemplateContextMiddleware
Utility Functions
def truncate(text: str, length: int = 100, suffix: str = "...") -> str
def format_datetime(value: datetime, fmt: str = "%Y-%m-%d %H:%M:%S") -> str
def static_hash(filepath: str) -> str
def merge_dicts(*dicts: Dict[str, Any]) -> Dict[str, Any]
Advanced Templating
This guide covers advanced features and patterns for the Nexios templating system.
Custom Filters
Create and register custom template filters:
from nexios.templating import TemplateConfig
def markdown_to_html(text: str) -> str:
import markdown
return markdown.markdown(text)
def currency(value: float, symbol: str = "$") -> str:
return f"{symbol}{value:,.2f}"
config = TemplateConfig(
custom_filters={
"markdown": markdown_to_html,
"currency": currency
}
)
Usage in templates:
{{ post.content|markdown }}
{{ product.price|currency("€") }}
Macros
Create reusable template components:
{# macros/forms.html #}
{% macro input(name, value='', type='text', label='') %}
<div class="form-group">
{% if label %}
<label for="{{ name }}">{{ label }}</label>
{% endif %}
<input type="{{ type }}" name="{{ name }}"
value="{{ value }}" id="{{ name }}">
</div>
{% endmacro %}
{# Usage in templates #}
{% from "macros/forms.html" import input %}
<form method="post">
{{ input('username', label='Username') }}
{{ input('password', type='password', label='Password') }}
</form>
Async Template Functions
Create async template functions for database queries or API calls:
from nexios.templating import TemplateConfig
from functools import partial
async def get_user_posts(user_id: int):
# Async database query
return await db.query("SELECT * FROM posts WHERE user_id = $1", user_id)
config = TemplateConfig(
custom_globals={
"get_posts": get_user_posts
}
)
Usage in templates:
{% set posts = await get_posts(user.id) %}
{% for post in posts %}
<article>{{ post.title }}</article>
{% endfor %}
Context Processors
Advanced context processor patterns:
from typing import Dict, Any
from nexios.templating.middleware import template_context
from nexios.cache import cached
class ContextBuilder:
def __init__(self):
self.processors = []
def add(self, processor):
self.processors.append(processor)
return self
async def build(self, request) -> Dict[str, Any]:
context = {}
for proc in self.processors:
context.update(await proc(request))
return context
@cached(ttl=300) # Cache for 5 minutes
async def get_site_stats(request):
return {
"total_users": await db.count("users"),
"total_posts": await db.count("posts")
}
async def get_user_data(request):
if user := await get_current_user(request):
return {
"user": user,
"notifications": await get_user_notifications(user.id)
}
return {}
# Combine processors
context_builder = (
ContextBuilder()
.add(get_site_stats)
.add(get_user_data)
)
app.add_middleware(template_context(
context_processor=context_builder.build
))
Template Caching
Implement template fragment caching:
from nexios.cache import Cache
from nexios.templating import TemplateConfig
cache = Cache()
async def cached_fragment(key: str, ttl: int = 300):
"""Template fragment cache."""
if content := await cache.get(key):
return content
return None
config = TemplateConfig(
custom_globals={
"cached_fragment": cached_fragment,
"cache": cache
}
)
Usage in templates:
{% set cache_key = 'sidebar_' + user.id %}
{% set cached = await cached_fragment(cache_key) %}
{% if cached %}
{{ cached }}
{% else %}
{% set content %}
{# Expensive sidebar rendering #}
<aside>...</aside>
{% endset %}
{{ cache.set(cache_key, content, ttl=300) }}
{{ content }}
{% endif %}
Error Handling
Custom error templates and handling:
from nexios.templating import render
from nexios.exceptions import TemplateError
@app.exception_handler(404)
async def not_found(request, exc):
return await render(
"errors/404.html",
{"path": request.url.path},
status_code=404
)
@app.exception_handler(TemplateError)
async def template_error(request, exc):
return await render(
"errors/template.html",
{
"error": str(exc),
"template": exc.template_name,
"lineno": exc.lineno
},
status_code=500
)
Testing Templates
Write tests for your templates:
import pytest
from nexios.templating import TemplateEngine, TemplateConfig
from nexios.testing import TestClient
@pytest.fixture
def template_engine():
config = TemplateConfig(template_dir="tests/templates")
return TemplateEngine(config)
async def test_template_rendering(template_engine):
content = await template_engine.render(
"welcome.html",
{"name": "User"}
)
assert "Welcome, User!" in content
async def test_template_filters(template_engine):
content = await template_engine.render(
"product.html",
{"price": 99.99}
)
assert "€99.99" in content
async def test_context_middleware(client: TestClient):
response = await client.get("/")
assert response.status_code == 200
assert "Welcome, Test User!" in response.text