Skip to content

Commit d5d66a3

Browse files
authored
fix(py/handlebarrz): helpers support in handlebarrz (#291)
* feat: python: Fix helpers support in Handlebars ISSUE: #284 CHANGELOG: [x] implement a render context helper Python wrapper [x] modify helpers to be aligned with the new HelperFn interface * feat: python: Fix helpers support in Handlebars ISSUE: #284 CHANGELOG: [x] implement a render context helper Python wrapper [x] modify helpers to be aligned with the new HelperFn interface * feat: python: Fix helper interface across relevant tests * Fix Rust lint warning * Change HandlebarrzHelper visibility in the helper wrapper. * Fix format warnings * Fix ruff preview warnings * Remove debug statements. * Address PR review comments.
1 parent e5b9056 commit d5d66a3

File tree

15 files changed

+326
-452
lines changed

15 files changed

+326
-452
lines changed

python/dotpromptz/src/dotpromptz/helpers.py

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,15 @@
3939
import json
4040
from typing import Any
4141

42-
from handlebarrz import Handlebars, HelperFn
42+
from handlebarrz import Handlebars, HelperFn, HelperOptions
4343

4444

45-
def json_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any]) -> str:
45+
def json_helper(params: list[Any], options: HelperOptions) -> str:
4646
"""Convert a value to a JSON string.
4747
4848
Args:
4949
params: List of values to convert to JSON
50-
hash_args: Hash arguments including formatting options.
51-
ctx: Current context options.
50+
options: Handlebars helper options.
5251
5352
Returns:
5453
JSON string representation of the value.
@@ -57,21 +56,22 @@ def json_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any
5756
return ''
5857

5958
obj = params[0]
60-
indent = hash_args.get('indent', 0)
61-
59+
indent = options.hash_value('indent') or 0
6260
try:
6361
if isinstance(indent, str):
6462
indent = int(indent)
6563
except (ValueError, TypeError):
6664
indent = 0
6765

6866
try:
67+
if indent == 0:
68+
return json.dumps(obj, separators=(',', ':'))
6969
return json.dumps(obj, indent=indent)
7070
except (TypeError, ValueError):
7171
return '{}'
7272

7373

74-
def role_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any]) -> str:
74+
def role_helper(params: list[Any], options: HelperOptions) -> str:
7575
"""Create a dotprompt role marker.
7676
7777
Example:
@@ -81,8 +81,7 @@ def role_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any
8181
8282
Args:
8383
params: List of values.
84-
hash_args: Hash arguments.
85-
ctx: Current context options.
84+
options: Handlebars helper options.
8685
8786
Returns:
8887
Role marker of the form `<<<dotprompt:role:...>>>`.
@@ -94,7 +93,7 @@ def role_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any
9493
return f'<<<dotprompt:role:{role_name}>>>'
9594

9695

97-
def history_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any]) -> str:
96+
def history_helper(params: list[Any], options: HelperOptions) -> str:
9897
"""Create a dotprompt history marker.
9998
10099
Example:
@@ -104,16 +103,15 @@ def history_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str,
104103
105104
Args:
106105
params: List of values.
107-
hash_args: Hash arguments including formatting options.
108-
ctx: Current context options.
106+
options: Handlebars helper options.
109107
110108
Returns:
111109
History marker of the form `<<<dotprompt:history>>>`.
112110
"""
113111
return '<<<dotprompt:history>>>'
114112

115113

116-
def section_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any]) -> str:
114+
def section_helper(params: list[Any], options: HelperOptions) -> str:
117115
"""Create a dotprompt section marker.
118116
119117
Example:
@@ -123,8 +121,7 @@ def section_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str,
123121
124122
Args:
125123
params: List of values.
126-
hash_args: Hash arguments including formatting options.
127-
ctx: Current context options.
124+
options: Handlebars helper options.
128125
129126
Returns:
130127
Section marker of the form `<<<dotprompt:section ...>>>`.
@@ -136,7 +133,7 @@ def section_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str,
136133
return f'<<<dotprompt:section {section_name}>>>'
137134

138135

139-
def media_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any]) -> str:
136+
def media_helper(params: list[Any], options: HelperOptions) -> str:
140137
"""Create a dotprompt media marker.
141138
142139
Example:
@@ -146,24 +143,23 @@ def media_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, An
146143
147144
Args:
148145
params: List of values.
149-
hash_args: Hash arguments including formatting options.
150-
ctx: Current context options.
146+
options: Handlebars helper options.
151147
152148
Returns:
153149
Media marker of the form `<<<dotprompt:media:url ...>>>`).
154150
"""
155-
url = hash_args.get('url', '')
151+
url = options.hash_value('url')
156152
if not url:
157153
return ''
158154

159-
content_type = hash_args.get('contentType', '')
155+
content_type = options.hash_value('contentType')
160156
if content_type:
161157
return f'<<<dotprompt:media:url {url} {content_type}>>>'
162158
else:
163159
return f'<<<dotprompt:media:url {url}>>>'
164160

165161

166-
def if_equals_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any]) -> str:
162+
def if_equals_helper(params: list[Any], options: HelperOptions) -> str:
167163
"""Compares two values and returns appropriate content.
168164
169165
Example:
@@ -176,8 +172,7 @@ def if_equals_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str
176172
```
177173
Args:
178174
params: List containing the two values to compare.
179-
hash_args: Hash arguments.
180-
ctx: Current context options.
175+
options: Handlebars helper options.
181176
182177
Returns:
183178
Rendered content based on equality check.
@@ -186,17 +181,10 @@ def if_equals_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str
186181
return ''
187182

188183
a, b = params[0], params[1]
189-
fn = ctx.get('fn')
190-
if a == b and fn is not None:
191-
return str(fn(ctx))
192-
else:
193-
inverse = ctx.get('inverse')
194-
if inverse is not None:
195-
return str(inverse(ctx))
196-
return ''
184+
return options.fn() if a == b else options.inverse()
197185

198186

199-
def unless_equals_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any]) -> str:
187+
def unless_equals_helper(params: list[Any], options: HelperOptions) -> str:
200188
"""Compares two values and returns appropriate content.
201189
202190
Example:
@@ -209,8 +197,7 @@ def unless_equals_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict
209197
```
210198
Args:
211199
params: List containing the two values to compare.
212-
hash_args: Hash arguments.
213-
ctx: Current context options.
200+
options: Handlebars helper options.
214201
215202
Returns:
216203
Rendered content based on inequality check.
@@ -219,14 +206,7 @@ def unless_equals_helper(params: list[Any], hash_args: dict[str, Any], ctx: dict
219206
return ''
220207

221208
a, b = params[0], params[1]
222-
fn = ctx.get('fn')
223-
if a != b and fn is not None:
224-
return str(fn(ctx))
225-
else:
226-
inverse = ctx.get('inverse')
227-
if inverse is not None:
228-
return str(inverse(ctx))
229-
return ''
209+
return options.fn() if a != b else options.inverse()
230210

231211

232212
BUILTIN_HELPERS: dict[str, HelperFn] = {

python/dotpromptz/tests/dotpromptz/dotprompt_test.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
from dotpromptz.dotprompt import Dotprompt, _identify_partials
4242
from dotpromptz.typing import ModelConfigT, ParsedPrompt, PromptMetadata, ToolDefinition
43-
from handlebarrz import HelperFn
43+
from handlebarrz import HelperFn, HelperOptions
4444

4545

4646
@pytest.fixture
@@ -79,7 +79,8 @@ def test_init_default(mock_handlebars: Mock) -> None:
7979
def test_init_with_options(mock_handlebars: Mock) -> None:
8080
"""Test initializing Dotprompt with custom options."""
8181

82-
def helper_fn(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any]) -> str:
82+
def helper_fn(params: list[Any], options: HelperOptions) -> str:
83+
"""Test helper."""
8384
return 'test_helper'
8485

8586
helpers: dict[str, HelperFn] = {'helper1': helper_fn}
@@ -98,7 +99,8 @@ def test_define_helper(mock_handlebars: Mock) -> None:
9899
"""Test defining a helper function."""
99100

100101
# This should match the signature of HelperFn.
101-
def helper_fn(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any]) -> str:
102+
def helper_fn(params: list[Any], options: HelperOptions) -> str:
103+
"""Test helper."""
102104
return 'test_helper'
103105

104106
dotprompt = Dotprompt()
@@ -168,7 +170,8 @@ def test_chainable_interface(mock_handlebars: Mock) -> None:
168170
inputSchema={'type': 'object'},
169171
)
170172

171-
def helper_fn(params: list[Any], hash_args: dict[str, Any], ctx: dict[str, Any]) -> str:
173+
def helper_fn(params: list[Any], options: HelperOptions) -> str:
174+
"""Test helper."""
172175
return 'helper1'
173176

174177
result = dotprompt.define_helper('helper1', helper_fn).define_partial('partial1', 'content').define_tool(tool_def)

0 commit comments

Comments
 (0)