Template Processing Pipeline for Foundation¶
Status: 💡 Proposal Date: 2025-01-25 Implementation: Not yet started Discussion: This is a forward-looking proposal for extracting plating's template engine into foundation
Overview¶
Plating's async template rendering with Jinja2 integration and partials support could become a general-purpose template processing module in foundation.
Proposed Foundation Module: provide.foundation.templates¶
Core Classes¶
from typing import Protocol, runtime_checkable
@runtime_checkable
class TemplateLoader(Protocol):
"""Protocol for template loading strategies."""
async def load_template(self, name: str) -> str:
"""Load template content by name."""
...
async def load_partials(self, template_name: str) -> dict[str, str]:
"""Load partial templates related to a template."""
...
class AsyncTemplateEngine:
"""Async template processing with partials support."""
def __init__(
self,
loader: TemplateLoader,
engine_type: str = "jinja2",
global_context: dict[str, Any] = None
):
self.loader = loader
self.engine_type = engine_type
self.global_context = global_context or {}
self._setup_engine()
async def render(
self,
template_name: str,
context: dict[str, Any],
partials: dict[str, str] = None
) -> str:
"""Render template with context and partials."""
# Async implementation with metrics tracking
async def render_batch(
self,
templates: list[tuple[str, dict[str, Any]]]
) -> list[str]:
"""Render multiple templates in parallel."""
# Parallel processing with rate limiting
def add_global_function(self, name: str, func: callable):
"""Add global template function."""
# Similar to plating's template function registration
class FileTemplateLoader:
"""Load templates from filesystem."""
def __init__(self, template_dirs: list[Path]):
self.template_dirs = template_dirs
async def load_template(self, name: str) -> str:
# Find and load template file
async def load_partials(self, template_name: str) -> dict[str, str]:
# Load related partial templates
class BundleTemplateLoader:
"""Load templates from resource bundles."""
def __init__(self, bundles: list[ResourceBundle]):
self.bundles = bundles
async def load_template(self, name: str) -> str:
# Load template from appropriate bundle
class CachingTemplateLoader:
"""Wrapper that adds caching to any loader."""
def __init__(self, loader: TemplateLoader, cache_ttl: int = 300):
self.loader = loader
self.cache_ttl = cache_ttl
self._cache = {}
Usage Examples¶
# Basic usage
from provide.foundation.templates import AsyncTemplateEngine, FileTemplateLoader
loader = FileTemplateLoader([Path("templates"), Path("partials")])
engine = AsyncTemplateEngine(loader)
# Add custom functions
engine.add_global_function("format_date", lambda d: d.strftime("%Y-%m-%d"))
# Render single template
result = await engine.render("documentation.md", {
"title": "API Docs",
"components": components_data
})
# Render multiple templates in parallel
results = await engine.render_batch([
("api.md", api_context),
("guide.md", guide_context),
("reference.md", ref_context)
])
# Using with resource bundles
from provide.foundation.discovery import ResourceDiscovery
discovery = ResourceDiscovery("*.templates")
bundles = discovery.discover_bundles(resource_type="documentation")
bundle_loader = BundleTemplateLoader(bundles)
cached_loader = CachingTemplateLoader(bundle_loader, cache_ttl=600)
engine = AsyncTemplateEngine(cached_loader)
Features¶
- Async-First: Built for high-performance parallel rendering
- Pluggable Loaders: Multiple template source strategies
- Caching Layer: Template compilation and content caching
- Metrics Integration: Built-in performance monitoring
- Multiple Engines: Support for Jinja2, Mako, etc.
- Partial Support: Modular template composition
- Global Functions: Extensible template function registry
- Rate Limiting: Controlled resource usage in batch operations
Advanced Features¶
# With resilience patterns
from provide.foundation.resilience import RetryExecutor, CircuitBreaker
engine = AsyncTemplateEngine(
loader=loader,
retry_executor=RetryExecutor(...),
circuit_breaker=CircuitBreaker(...)
)
# With streaming for large templates
async for chunk in engine.render_stream("large_template.html", context):
# Process template chunks as they're rendered
# With template inheritance and includes
engine.add_template_path("base_templates/")
result = await engine.render("child_template.html", context)
Benefits¶
- Performance: Async processing enables high throughput
- Flexibility: Multiple loader strategies for different use cases
- Reliability: Built-in resilience and caching
- Observability: Metrics and monitoring integration
- Reusability: Generic enough for many template use cases
Migration Path¶
- Extract plating's template logic to foundation
- Add async capabilities and loader abstractions
- Update plating to use foundation's template engine
- Provide compatibility layer for existing template code
- Add support for additional template engines