PyHandlebars

April 19, 2026 · View on GitHub

Python library for the handlebars templating language. It is a small wrapper around the handlebars-rust library.

Installation

Pyhandlebars is listed on PyPi:

pip install pyhandlebars

Getting Start

1. Simple Template

from pyhandlebars import Template

t: Template = Template("Hallo {{name}}")
rendered_text = t.format({"name": "world"})"

# Returns "Hello world"

2. Pydantic BaseModel Support

def test_simple_pydantic_support():
    from pyhandlebars import Template
    from pydantic import BaseModel

    class Person(BaseModel):
        name: str

    t: Template[Person] = Template("This is {{name}}!")
    rendered_text = t.format(Person(name="Alice"))
    assert rendered_text == "This is Alice!"

3. Default Global Client

Every Template created without an explicit client= argument shares a single process-wide global client. This means partials and helpers registered on one template are immediately available to all other templates that use the default client.

from pyhandlebars import Template

# Both templates share the global client — the partial is visible to t2.
_ = Template("Hi {{name}}", name="greeting")
t = Template("{{> greeting}}")
print(t.format({"name": "Bob"}))  # Hi Bob

4. Adding Helper Functions

PyHandlebars allows you to register your own helper functions. However, keep in mind that these are not as fast and performant as built-in helpers.

Each helper receives two arguments: params (a list of positional arguments from the template call) and context (the full data object passed to format).

Option A — PyHandlebars.helper on the global client

Access helper on the class (not an instance) to register directly on the global client — no explicit PyHandlebars() needed.

from pyhandlebars import PyHandlebars, Template

@PyHandlebars.helper
def shout(params: list, context: dict):
    return f"{params[0].upper()} from {context['location']}"

# Supply a custom template name with name=
@PyHandlebars.helper(name="whisper")
def my_whisper_fn(params: list, context: dict):
    return params[0].lower()

t = Template("{{shout name}} / {{whisper name}}")
assert t.format({"name": "Alice", "location": "Wonderland"}) == "ALICE from Wonderland / alice"

Option B — register_helper / @client.helper() on a dedicated client

Use an explicit PyHandlebars() instance when you want helpers isolated from the global client.

from pyhandlebars import PyHandlebars, Template

def shout(params: list, context: dict):
    return f"{params[0].upper()} from {context['location']}"

client = PyHandlebars()
client.register_helper("shout", shout)

# Decorator style:
@client.helper()
def whisper(params: list, context: dict):
    return params[0].lower()

t = Template("{{shout name}} / {{whisper name}}", client=client)
assert t.format({"name": "Alice", "location": "Wonderland"}) == "ALICE from Wonderland / alice"

5. More Examples

More examples can be found in the tests/test_examples.py file.

Supported Template Functions

The original HandlebarsJS supports different Built-in Helpers. PyHandlebars supports the following subset (given by handlebars-rust):

{{{{raw}}}} ... {{{{/raw}}}} Escape handlebars expression within the block

{{#if ...}} ... {{else}} ... {{/if}} if-else block
Boolean Operators can be used here, for example {{#if (gt 2 1)}} ...
- other operator: eq, ne, gt, gte, lt, lte, and, or, not

{{#unless ...}} ... {{else}} .. {{/unless}} if-not-else block

{{#each ...}} ... {{/each}} iterates over an array or object. Handlebars-rust doesn’t support mustache iteration syntax so use each instead.

{{#with ...}} ... {{/with}} change current context. Similar to {{#each}}, used for replace corresponding mustache syntax.

{{lookup ... ...}} get value from array or map by @index or @key

{{> ...}} include template by its name

{{len ...}} returns length of array/object/string

Comparison to other Template Engines in Python

We benchmarked PyHandlebars against 13 other Python template engines across 4 examples, running 1,000 iterations each. Each benchmark measures two phases:

  • Prepare — compiling / registering the template (done once per request cycle in a real app)
  • Render — filling data into the compiled template (the hot path)

Examples

ExampleWhat it tests
InvoiceSimple variable substitution and list iteration over line items with a nested address
User profileA boolean conditional (verified badge) combined with iteration over social links
Deployment reportNested object access and dictionary lookups — status labels and region info keyed by name
Release reportDeep nesting — components each containing a test suite and a dependency list, plus environment conditionals

Results

Each box shows the render-time distribution across 1,000 runs. Tools are sorted fastest → slowest by average render time. The top chart uses a linear scale; the bottom uses a log scale to make differences between fast engines visible.

Benchmark summary

Contribution

Any contribution to this library is welcomed. To get started into development! When you run into any problems, feel free to reach out to me.

Notes

I mainly setup the library for the use case of prompt templating. If you miss something, just send me a DM or feel free to jump in with a PR 💫

License

This library (PyHandlebars) is open sourced under the MIT License.