Skip to content

Python API Reference

Module Functions

render

Render a template string with a context dictionary:

from pydantic_handlebars import render

print(render('Hello {{name}}!', {'name': 'World'}))
#> Hello World!

Uses a default environment with standard Handlebars helpers (if, unless, each, with, lookup, log).

Render a Handlebars template string with the given context.

This is a convenience function that uses a default environment with standard Handlebars helpers registered.

Parameters:

Name Type Description Default
source str

The Handlebars template string.

required
context dict[str, Any] | None

The data context for rendering.

None
strict bool

If True, raise :class:HandlebarsRuntimeError when a referenced path is not present on the context. An explicit None value is still allowed. Defaults to False (missing references render as empty strings, matching Handlebars.js default behaviour).

False
open_delim str

Open mustache delimiter. Defaults to {{. Non-default pairs let you embed Handlebars output that should preserve {{...}}-shaped placeholders for a later rendering pass.

'{{'
close_delim str

Close mustache delimiter. Defaults to }}.

'}}'

Returns:

Type Description
str

The rendered string.

Example
result = render('Hello {{name}}!', {'name': 'World'})
assert result == 'Hello World!'

# Non-default delimiters leave the literal `{{user}}` alone:
result = render('Hi @{name}@, see {{user}} later', {'name': 'A'},
               open_delim='@{', close_delim='}@')
assert result == 'Hi A, see {{user}} later'

compile

Compile a template into a reusable callable. When called with just a source string, returns a CompiledTemplate. When called with a source string and a type, validates the template against the type's JSON schema and returns a CompiledTypedTemplate:

from pydantic_handlebars import compile

# Untyped compilation
template = compile('Hello {{name}}!')
print(template.render({'name': 'World'}))
#> Hello World!
from pydantic import BaseModel

from pydantic_handlebars import compile


class User(BaseModel):
    name: str
    age: int


# Typed compilation — validates template against User's schema
template = compile('Hello {{name}}, age {{age}}!', User)
print(template.render(User(name='Alice', age=30)))
#> Hello Alice, age 30!

Compile a Handlebars template string into a reusable callable.

When called with just a source string, returns a CompiledTemplate. When called with a source string and a type, validates the template against the type's JSON schema and returns a CompiledTypedTemplate.

Parameters:

Name Type Description Default
source str

The Handlebars template string.

required
tp type[Any] | None

Optional Pydantic model type or any type supported by TypeAdapter.

None
serialize_as_any bool

If True, serialize subclass fields instead of only the declared type's fields. Only applies when tp is provided.

False

Returns:

Type Description
CompiledTemplate | CompiledTypedTemplate[Any]

A CompiledTemplate or CompiledTypedTemplate callable.

Example
template = compile('Hello {{name}}!')
result = template.render({'name': 'World'})
assert result == 'Hello World!'

typed_compiler

Create a TypedCompiler for compiling multiple templates against the same type. Caches the type adapter and JSON schema for efficiency:

from pydantic import BaseModel

from pydantic_handlebars import typed_compiler


class User(BaseModel):
    name: str
    age: int


compiler = typed_compiler(User)

greeting = compiler.compile('Hello {{name}}!')
info = compiler.compile('{{name}} is {{age}} years old.')

user = User(name='Alice', age=30)
print(greeting.render(user))
#> Hello Alice!
print(info.render(user))
#> Alice is 30 years old.

Create a typed compiler for repeated compilation against a type.

Parameters:

Name Type Description Default
tp type[T]

The Pydantic model type or any type supported by TypeAdapter.

required
env HandlebarsEnvironment | None

Optional HandlebarsEnvironment. Uses a default if not provided.

None
optional_field_severity IssueSeverity

Severity for optional field access issues.

'error'
serialize_as_any bool

If True, serialize subclass fields instead of only the declared type's fields.

False

Returns:

Type Description
TypedCompiler[T]

A TypedCompiler instance.

check_template_compatibility

Validate a template (or list of templates) against a JSON schema without compiling:

from pydantic_handlebars import check_template_compatibility

schema = {
    'type': 'object',
    'properties': {'name': {'type': 'string'}},
    'required': ['name'],
}

result = check_template_compatibility('Hello {{name}}!', schema)
print(result.is_compatible)
#> True

result = check_template_compatibility('{{missing_field}}', schema)
print(result.is_compatible)
#> False
print(result.issues[0].field_path)
#> missing_field

Check template compatibility with a JSON schema.

Parses the templates, walks their ASTs, and verifies every field reference exists in the provided JSON schema.

Parameters:

Name Type Description Default
templates str | list[str]

A single template string or list of template strings.

required
json_schema _JsonSchema

The JSON schema to validate against (e.g., from Pydantic's model_json_schema()).

required
raise_on_error bool

If True, raise TemplateSchemaError when incompatible.

False
optional_field_severity IssueSeverity

Severity for fields not found in the schema when the schema has properties but the field is not in required. Defaults to ERROR.

'error'
helpers set[str] | None

Additional helper names to treat as known (beyond built-in helpers).

None

Returns:

Type Description
CompatibilityResult

A CompatibilityResult with any issues found.

Raises:

Type Description
TemplateSchemaError

If raise_on_error is True and there are ERROR-level issues.

extract_dependencies

Statically walk a template and return the set of top-level context field names it depends on. No rendering, no context required:

from pydantic_handlebars import extract_dependencies

extract_dependencies('{{greeting}}, {{user.name}}!')
#> {'greeting', 'user'}

# Scope-aware: `each` / `with` shift the context, so inner refs are not top-level deps.
extract_dependencies('{{#each items}}{{name}}{{/each}}')
#> {'items'}

# `../` walks back to the top scope and contributes a top-level dep:
extract_dependencies('{{#each items}}{{../top}}{{/each}}')
#> {'items', 'top'}

Return the set of top-level context field names source depends on.

Statically walks the template AST — no rendering or context is required. Useful for tooling that needs to know "what fields does this template expect" without actually executing it: dependency resolution, prompt composition systems, dataset generation, etc.

Parameters:

Name Type Description Default
source str

Handlebars template source.

required
helpers set[str] | None

Names of additional helpers to recognise. Names registered as helpers are not treated as context references — for example, {{format date}} records date as a dependency but not format.

None
include_extra_helpers bool

When True, also treat the non-spec helpers registered by HandlebarsEnvironment(extra_helpers=True) (json, uppercase, eq, …) as known helpers. Match this to the environment you intend to render with.

False
open_delim str

Open mustache delimiter. Defaults to {{. Pass the same delimiters you'd render the template with.

'{{'
close_delim str

Close mustache delimiter. Defaults to }}.

'}}'

Returns:

Type Description
set[str]

Set of top-level context field names referenced by the template.

set[str]

Empty if the template only contains literal content, @data

set[str]

variables, or references shadowed by block parameters.

Raises:

Type Description
HandlebarsParseError

If the template cannot be parsed.

Examples:

extract_dependencies('Hello {{user_name}}!') == {'user_name'}
extract_dependencies('{{user.name}} <{{user.email}}>') == {'user'}
extract_dependencies('{{#if beta}}new{{else}}{{tagline}}{{/if}}') == {'beta', 'tagline'}
extract_dependencies('{{#each items}}{{name}}{{/each}}') == {'items'}
extract_dependencies('{{#each items}}{{../top}}{{/each}}') == {'items', 'top'}

Custom delimiters

HandlebarsEnvironment, render, compile, and extract_dependencies all accept matching open_delim and close_delim keyword parameters (defaulting to {{ and }}). Custom delimiter pairs let you embed a Handlebars rendering pass without disturbing a different set of placeholders intended for a later pass:

from pydantic_handlebars import render

# Compose with @{...}@ — leaves {{...}} literals untouched for the next pass.
after_composition = render(
    'You are helping @{role}@. Greet {{user_name}}.',
    {'role': 'a designer'},
    open_delim='@{', close_delim='}@',
)
print(after_composition)
#> You are helping a designer. Greet {{user_name}}.

print(render(after_composition, {'user_name': 'Alice'}))
#> You are helping a designer. Greet Alice.

All standard syntax — block helpers, whitespace control, comments, escape sequences, and the ampersand-unescape variant — works under custom delimiters. Triple-stache ({{{...}}}) and raw blocks ({{{{...}}}}) are only available with the default delimiter pair; under custom delimiters use @{&x}@ for per-expression escape opt-out, or auto_escape=False on the environment.

The public delimiter parameters reject empty, identical, whitespace-edged, and expression-syntax- conflicting ("'()=|~) pairs.

Strict mode

Pass strict=True to fail loudly when a template references a path that does not exist on the context, instead of silently rendering as the empty string:

from pydantic_handlebars import HandlebarsRuntimeError, render

try:
    render('Hello {{name}}!', {}, strict=True)
except HandlebarsRuntimeError as e:
    print(e)
    #> Strict mode: 'name' references missing context key 'name'

Strict mode treats missing keys as errors but lets explicit None values through. It applies to block-helper arguments ({{#if missing}}) and @data variables the same way it applies to direct lookups. Helper names, block params, and {{this}} are unaffected.

Strict mode can also be set at the environment level: HandlebarsEnvironment(strict=True).

Classes

HandlebarsEnvironment

An environment for rendering templates with custom helpers.

from pydantic_handlebars import HandlebarsEnvironment, HelperOptions

env = HandlebarsEnvironment(extra_helpers=True)


@env.helper
def shout(*args: object, options: HelperOptions) -> str:
    return str(args[0]).upper() + '!!!'


print(env.render('{{shout name}}', {'name': 'world'}))
#> WORLD!!!

Pass extra_helpers=True to include additional utility, comparison, and boolean helpers beyond the 6 standard Handlebars.js helpers. See Built-in Helpers for the full list.

An environment for rendering Handlebars templates with custom helpers.

The environment manages a set of helpers (both built-in and user-registered) and provides methods for compiling and rendering templates.

Example
env = HandlebarsEnvironment()


@env.helper
def shout(*args: object, options: HelperOptions) -> str:
    return str(args[0]).upper() + '!!!'


result = env.render('{{shout name}}', {'name': 'world'})
assert result == 'WORLD!!!'

open_delim property

The open mustache delimiter this environment compiles templates with.

Defaults to {{. Templates compiled or rendered through this environment look for this string to mark the start of an expression.

close_delim property

The close mustache delimiter this environment compiles templates with.

Defaults to }}. Non-default delimiter pairs disable the triple-stache ({{{...}}}) and raw-block ({{{{...}}}}) variants since those forms only have unambiguous extensions for the default pair.

strict property

Whether strict mode is enabled for templates rendered by this environment.

When True, referencing a path that does not exist on the context raises :class:HandlebarsRuntimeError instead of silently rendering as an empty string. An explicit None value in the context is still allowed — strict mode only fires for genuinely missing keys.

auto_escape property

Whether HTML auto-escaping is enabled for {{expression}} output.

When False (the default), no escaping is applied — appropriate for non-HTML use cases like prompt generation. When True, double-stache expressions are HTML-escaped. Triple-stache {{{expression}}} is always unescaped regardless of this setting.

helper(fn)

helper(fn: F) -> F
helper(fn: str) -> Callable[[F], F]

Register a helper function.

Can be used as a decorator (with or without a name argument):

@env.helper
def my_helper(*args: object, options: HelperOptions) -> str:
    return str(args[0]).upper()


@env.helper('custom-name')
def my_helper(*args: object, options: HelperOptions) -> str:
    return str(args[0]).upper()

Parameters:

Name Type Description Default
fn F | str

The helper function, or a name string for use as a decorator factory.

required

Returns:

Type Description
F | Callable[[F], F]

The registered function, or a decorator.

register_helper(name, fn)

Register a helper function with an explicit name.

Parameters:

Name Type Description Default
name str

The name to register the helper under.

required
fn HelperFunc

The helper function.

required

unregister_helper(name)

Unregister a helper by name.

Parameters:

Name Type Description Default
name str

The name of the helper to remove.

required

Raises:

Type Description
KeyError

If the helper is not registered.

compile(source)

Compile a template string into a reusable callable.

Parameters:

Name Type Description Default
source str

The Handlebars template string.

required

Returns:

Type Description
CompiledTemplate

A CompiledTemplate that takes a context dict and returns a rendered string.

render(source, context=None)

Render a template string with the given context.

Parameters:

Name Type Description Default
source str

The Handlebars template string.

required
context Any

The data context for rendering.

None

Returns:

Type Description
str

The rendered string.

CompiledTemplate

Returned by compile(source) (without a type). Call .render() with a context dict.

A compiled Handlebars template.

render(context=None)

Render the template with the given context.

CompiledTypedTemplate

Returned by compile(source, Type). Call .render() with an instance of the type, or .validate_and_render() with unvalidated data:

from pydantic import BaseModel

from pydantic_handlebars import compile


class User(BaseModel):
    name: str
    age: int


template = compile('Hello {{name}}!', User)

# .render() — expects a validated instance
print(template.render(User(name='Alice', age=30)))
#> Hello Alice!

# .validate_and_render() — validates input through Pydantic first
print(template.validate_and_render({'name': 'Bob', 'age': 25}))
#> Hello Bob!

A compiled template bound to a specific type.

Created by TypedCompiler.compile() or compile(source, tp).

render(data)

Render with typed data (no runtime validation).

Parameters:

Name Type Description Default
data T

The data to render. Must match type T.

required

Returns:

Type Description
str

The rendered template string.

validate_and_render(data)

Validate data through Pydantic, then render.

Parameters:

Name Type Description Default
data Any

The data to validate and render. Can be any type that Pydantic can coerce to T.

required

Returns:

Type Description
str

The rendered template string.

TypedCompiler

Returned by typed_compiler(). Caches the type adapter and JSON schema for compiling multiple templates against the same type.

Compiler for creating typed templates against a Pydantic model.

Caches the TypeAdapter and JSON schema for repeated compilation against the same type.

Parameters:

Name Type Description Default
tp type[T]

The Pydantic model type or any type supported by TypeAdapter.

required
env HandlebarsEnvironment | None

Optional HandlebarsEnvironment. Uses a default if not provided.

None
optional_field_severity IssueSeverity

Severity for optional field access issues.

'error'
Example
from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int

compiler = TypedCompiler(User)
template = compiler.compile('Hello {{name}}, you are {{age}}!')
result = template.render(User(name='Alice', age=30))
assert result == 'Hello Alice, you are 30!'

json_schema property

The JSON schema derived from the type.

compile(source, *, raise_on_error=True)

Compile a template, checking compatibility with the schema.

Parameters:

Name Type Description Default
source str

The Handlebars template string.

required
raise_on_error bool

If True (default), raise on incompatible templates.

True

Returns:

Type Description
CompiledTypedTemplate[T]

A CompiledTypedTemplate ready for rendering.

Raises:

Type Description
TemplateSchemaError

If the template references fields not in the schema.

HelperOptions

Passed to block helpers as the options keyword argument. Provides access to the block body, inverse block, hash arguments, and data variables.

from pydantic_handlebars import HandlebarsEnvironment, HelperOptions

env = HandlebarsEnvironment()


@env.helper('repeat')
def repeat_helper(*args: object, options: HelperOptions) -> str:
    times = int(options.hash.get('times', 1))
    return ''.join(options.fn() for _ in range(times))


print(env.render('{{#repeat times=3}}Ho {{/repeat}}', {}))
#> Ho Ho Ho

Options passed to helper functions.

Attributes:

Name Type Description
fn

Function to render the block content with a given context.

inverse

Function to render the else/inverse content with a given context.

hash dict[str, Any]

Hash arguments passed to the helper.

data dict[str, Any]

Data variables (@root, @index, etc.).

block_params list[str]

Block parameter values.

name property

Get the helper name (for the log helper).

SafeString

Marks a string as already HTML-safe when returned from helpers. When auto_escape=True, helper return values wrapped in SafeString bypass HTML escaping:

from pydantic_handlebars import HandlebarsEnvironment, HelperOptions, SafeString

env = HandlebarsEnvironment(auto_escape=True)


@env.helper
def bold(*args: object, options: HelperOptions) -> SafeString:
    return SafeString(f'<b>{args[0]}</b>')


print(env.render('{{bold name}}', {'name': 'Alice'}))
#> <b>Alice</b>

Note

SafeString values in context dicts are serialized to plain str before rendering, losing the safe marker. SafeString is primarily useful as a return type from helpers.

A string subclass that marks content as safe (no HTML escaping).

When a SafeString is rendered in a {{expression}} context, it will NOT be HTML-escaped, even though normal strings would be.

CompatibilityResult

Returned by check_template_compatibility. Contains is_compatible (bool) and issues (list of TemplateIssue).

Result of checking template compatibility with a JSON schema.

Attributes:

Name Type Description
issues list[TemplateIssue]

List of all issues found during checking.

is_compatible property

Whether the templates are compatible (no ERROR-level issues).

TemplateIssue

A single issue found during schema compatibility checking. Contains field_path, message, and severity.

A single compatibility issue found during template checking.

Attributes:

Name Type Description
severity IssueSeverity

The severity level of the issue.

message str

A human-readable description of the issue.

field_path str

The dotted path to the field that caused the issue.

template_index int

The index of the template that contains the issue.

Exceptions

HandlebarsError

Base exception for all pydantic_handlebars errors.

Base exception for all Handlebars errors.

HandlebarsParseError

Raised when a template cannot be parsed. Includes line and column attributes:

from pydantic_handlebars import HandlebarsParseError, render

try:
    render('{{{unclosed}', {})
except HandlebarsParseError as e:
    assert e.line is not None
    assert e.column is not None

Raised when a template cannot be parsed.

Attributes:

Name Type Description
message

The error message.

line

The line number where the error occurred (1-based).

column

The column number where the error occurred (1-based).

HandlebarsRuntimeError

Raised during template rendering (e.g., exceeding depth limits).

Raised when an error occurs during template rendering.

TemplateSchemaError

Raised by compile(source, Type) when the template references fields not present in the type's JSON schema:

from pydantic import BaseModel

from pydantic_handlebars import TemplateSchemaError, compile


class User(BaseModel):
    name: str


try:
    compile('{{missing}}', User)
except TemplateSchemaError as e:
    print(e)
    """
    1 error(s) found:
      - missing: Field 'missing' not found in schema
    """

Raised when templates are incompatible with the provided JSON schema.

Utilities

escape_expression

HTML-escape a string using Handlebars escaping rules:

from pydantic_handlebars import escape_expression

print(escape_expression('<b>bold</b>'))
#> &lt;b&gt;bold&lt;/b&gt;

Escape a string for safe inclusion in HTML.

This matches the Handlebars.js escapeExpression function exactly.

Parameters:

Name Type Description Default
value str

The string to escape.

required

Returns:

Type Description
str

The HTML-escaped string.