Skip to main content

dotpromptz-handlebars

A pure-Python Handlebars implementation, owned by the Genkit team. It implements the subset of Handlebars that Dotprompt templates use, not the full language.

Dotprompt is language-agnostic. A .prompt file should render the same way in every SDK, so what a prompt does is defined by the file, not by the language running it. To keep that true, this package treats Handlebars.js 4.7.9 as the spec. Handlebars has no standalone spec. Handlebars.js is the reference implementation that the other ports follow, so where implementations disagree, Handlebars.js wins. 4.7.9 is the latest Handlebars.js release, and it's what the Dotprompt JS SDK's ^4.7.8 range resolves to on a fresh install. Conformance tests check this package's output against it.

No Rust or Node at runtime.

uv add dotpromptz-handlebars

Quick start

from dotpromptz_handlebars import Handlebars

# 1. Compile once
hb = Handlebars()
render = hb.compile(
    'Table for {{party}}. '
    '{{#if allergies}}Avoid: {{#each allergies}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}.{{/if}}'
)

# 2. Render many times
print(render({'party': 4, 'allergies': ['peanuts', 'shellfish']}))
# => Table for 4. Avoid: peanuts, shellfish.

print(render({'party': 2, 'allergies': []}))
# => Table for 2.

compile() returns a function. Call it with your input dict. Pass data= for values the template reads with @:

render = hb.compile('{{name}} / {{@request_id}}')
print(render({'name': 'Ana'}, data={'request_id': 'r-42'}))
# => Ana / r-42

Helpers

A helper is fn(args, options). args holds the positional arguments and options.hash holds the key=value ones.

def price(args, options):
    cents = args[0]
    currency = options.hash.get('currency', 'USD')
    return f'{cents / 100:.2f} {currency}'


hb.register_helper('price', price)
print(hb.compile('Salmon: {{price cents currency="EUR"}}')({'cents': 1850}))
# => Salmon: 18.50 EUR

For a block helper ({{#name}}...{{/name}}), options.fn(context) renders the body and options.inverse(context) renders the {{else}} body:

def loud(args, options):
    return options.fn(options.context).upper()


hb.register_helper('loud', loud)
print(hb.compile('{{#loud}}chef says {{dish}}{{/loud}}')({'dish': 'tartine'}))
# => CHEF SAYS TARTINE

options.context holds the current scope. For backward compatibility with handlebarrz, calling options.context() returns the dictionary.

Helper output gets escaped like any other value. Return a SafeString to insert markup as written. Escaping is then up to you.

if, unless, each, with, lookup, and log are built in. You can't override them with register_helper.

Partials

hb.register_partial('dish', '- {{name}} ({{price}})\n')
render = hb.compile('Menu:\n{{#each dishes}}{{> dish}}{{/each}}')
print(render({'dishes': [{'name': 'Tartine', 'price': 14}, {'name': 'Soup', 'price': 9}]}))
# => Menu:
#    - Tartine (14)
#    - Soup (9)

A missing partial raises. {{#> name}}fallback{{/name}} renders the fallback instead. Inline partials ({{#*inline "name"}}) and dynamic partials ({{> (helperName)}}) work too.

Escaping

{{value}} HTML-escapes by default. {{{value}}} doesn't.

render = Handlebars().compile('{{note}} | {{{note}}}')
print(render({'note': 'Fish & <b>Chips</b>'}))
# => Fish &amp; &lt;b&gt;Chips&lt;/b&gt; | Fish & <b>Chips</b>

Prompts usually go to a model, not a browser. To turn escaping off everywhere, use Handlebars(escape_html=False).

Strict mode

Handlebars(strict=True) raises when the template prints a path that isn't in the input:

from dotpromptz_handlebars import Handlebars, StrictModeError

try:
    Handlebars(strict=True).compile('Hello {{user.name}}')({'user': {}})
except StrictModeError as err:
    print(err.path)
# => user.name

Conditions don't raise. {{#if user.name}} on a missing path takes the else branch.

Values that surprise Python developers

Dotprompt defines what a template means, independent of the language that renders it. Dotprompt templates use Handlebars semantics, and this package takes them from Handlebars.js 4.7.9. Conditionals and printed values follow those semantics, not Python's bool() or str():

  • 0 fails {{#if}} but enters {{#with}}. Use {{#if n includeZero=true}} to keep 0.
  • {} enters {{#if}}. {{#if}} takes the else branch only for a missing key, None, False, "", 0, or [].
  • Lists print as 1,2 and dicts print as [object Object]. Format structured data with a helper. Dotprompt ships {{json value}} for this.
  • 1.0 prints as 1, and False prints as false.

What's not supported

These raise ValueError:

  • Decorators ({{* name}}).
  • Functions in the input dict. Register them with register_helper, so a template can only call code you chose.
  • Broken templates. Which templates fail matches Handlebars.js, but the error messages are worded differently.

Everything else a prompt uses is supported:

  • Paths: user.name, ../name, items.[0]
  • Blocks: if, unless, each, with, with else and else if
  • Block parameters: as |item index|
  • Data: @index, @key, @first, @last, @root
  • Syntax: subexpressions, comments, ~ whitespace control, and raw blocks ({{{{raw}}}})

Metadata

Release files for dotpromptz-handlebars 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dotpromptz-handlebars 0.2.0
File Size Uploaded
dotpromptz_handlebars-0.2.0.tar.gz 41.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dotpromptz-handlebars 0.2.0
File Interpreter ABI Platform
dotpromptz_handlebars-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 61.1 kB

Release files / dotpromptz_handlebars-0.2.0.tar.gz

Download URL dotpromptz_handlebars-0.2.0.tar.gz
Size 41.1 kB
Tags Source
SHA-256 checksum
How to use checksums
62528d768c9461f64fdf119a39121ebd0e6fec04f1a3578c7e3b832512d78423
BLAKE2b-256 checksum
How to use checksums
2240c50f14ab97363667944c59f7bddc0c878068fc51ce5fa9fcdb4b5dffa45f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release files / dotpromptz_handlebars-0.2.0-py3-none-any.whl

Download URL dotpromptz_handlebars-0.2.0-py3-none-any.whl
Size 20.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7da43a27a9fe095379d887ee6cf3ab4f2aefa43458064bee0dcf08282ed73c8c
BLAKE2b-256 checksum
How to use checksums
c169bd20ac7246569d4634c052d5af3c1f06c82a2234e1f619c2cc36d33561e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.9

1 release file

0.1.8

38 release files

0.1.7

38 release files

0.1.5

38 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page