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 & <b>Chips</b> | 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():
0fails{{#if}}but enters{{#with}}. Use{{#if n includeZero=true}}to keep0.{}enters{{#if}}.{{#if}}takes the else branch only for a missing key,None,False,"",0, or[].- Lists print as
1,2and dicts print as[object Object]. Format structured data with a helper. Dotprompt ships{{json value}}for this. 1.0prints as1, andFalseprints asfalse.
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, withelseandelse 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)
| File | Size | Uploaded | |
|---|---|---|---|
| dotpromptz_handlebars-0.2.0.tar.gz | 41.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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