Skip to content

Commit 0af9a64

Browse files
authored
feat(py/dotpromptz): construct test-specific dotprompt instance with partials and partial resolver set up (#275)
1 parent aef7d6b commit 0af9a64

File tree

3 files changed

+77
-33
lines changed

3 files changed

+77
-33
lines changed

.github/workflows/python.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ jobs:
8383
- name: Check licenses
8484
run: ./scripts/check_license
8585

86-
- name: Run Rust tests for handlebarrz
87-
run: ./scripts/run_handlebarrz_tests
86+
- name: Run Rust checks for handlebarrz
87+
run: ./scripts/run_handlebarrz_checks
8888

8989
- name: Build Rust extension for Python ${{ matrix.python-version }}
9090
working-directory: ./python

python/dotpromptz/tests/dotpromptz/spec_test.py

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ class SpecSuite(TypedDict, total=False):
158158
'spec/helpers/section.yaml',
159159
'spec/helpers/unlessEquals.yaml',
160160
'spec/metadata.yaml',
161-
#'spec/partials.yaml',
162-
#'spec/picoschema.yaml',
161+
'spec/partials.yaml',
162+
'spec/picoschema.yaml',
163163
'spec/variables.yaml',
164164
]
165165

@@ -185,25 +185,25 @@ class TestSpecFiles(unittest.IsolatedAsyncioTestCase):
185185

186186
def test_spec_path(self) -> None:
187187
"""Test that the spec directory exists."""
188-
assert SPECS_DIR.exists()
189-
assert SPECS_DIR.is_dir()
188+
self.assertTrue(SPECS_DIR.exists())
189+
self.assertTrue(SPECS_DIR.is_dir())
190190

191191
def test_spec_path_contains_yaml_files(self) -> None:
192192
"""Test that the spec directory contains YAML files."""
193-
assert list(SPECS_DIR.glob('**/*.yaml'))
193+
self.assertTrue(list(SPECS_DIR.glob('**/*.yaml')))
194194

195195
def test_spec_files_are_valid(self) -> None:
196196
"""Test that all spec files contain valid YAML."""
197197
for file in SPECS_DIR.glob('**/*.yaml'):
198198
with open(file) as f:
199199
data = yaml.safe_load(f)
200-
assert data is not None
200+
self.assertIsNotNone(data)
201201

202202
async def test_specs(self) -> None:
203203
"""Discovers and runs all YAML specification tests."""
204204
for yaml_file in SPECS_DIR.glob('**/*.yaml'):
205205
if not is_allowed_spec_file(yaml_file):
206-
logger.debug(
206+
logger.warn(
207207
'Skipping spec file',
208208
file=yaml_file,
209209
)
@@ -214,14 +214,57 @@ async def test_specs(self) -> None:
214214

215215
for suite_data_raw in suites_data:
216216
suite: SpecSuite = cast(SpecSuite, suite_data_raw)
217+
suite_name: str = suite.get('name', f'UnnamedSuite_in_{yaml_file.name}')
217218

218-
with self.subTest(suite=suite.get('name', 'UnnamedSuite')):
219-
for test_case_data_raw in suite.get('tests', []):
220-
test_case: SpecTest = test_case_data_raw
219+
suite['name'] = suite_name
221220

222-
with self.subTest(test=test_case.get('desc', 'UnnamedTest')):
223-
dotprompt = Dotprompt()
224-
await self.run_yaml_test(yaml_file, dotprompt, suite, test_case)
221+
with self.subTest(suite=suite_name):
222+
for tc_raw in suite.get('tests', []):
223+
tc: SpecTest = tc_raw
224+
tc_name = tc.get('desc', f'UnnamedTest_in_{suite_name}')
225+
tc['desc'] = tc_name
226+
227+
with self.subTest(test=tc_name):
228+
# TODO: Doing this per test case is safer for
229+
# test sandboxing but we could perhaps do this
230+
# per suite as well.
231+
dotprompt = self.make_dotprompt(suite)
232+
await self.run_yaml_test(yaml_file, dotprompt, suite, tc)
233+
234+
def make_dotprompt(self, suite: SpecSuite) -> Dotprompt:
235+
"""Constructs and sets up a Dotprompt instance for the given suite.
236+
237+
Args:
238+
suite: The suite to set up the Dotprompt for.
239+
240+
Returns:
241+
A Dotprompt instance configured for the given suite.
242+
"""
243+
resolver_partials: dict[str, str] = suite.get('resolver_partials', {})
244+
245+
def partial_resolver_fn(name: str) -> str | None:
246+
"""Resolves a partial name to a template string.
247+
248+
Args:
249+
name: The name of the partial to resolve.
250+
251+
Returns:
252+
The template string for the partial, or None if the partial is not found.
253+
"""
254+
return resolver_partials.get(name)
255+
256+
dotprompt = Dotprompt(
257+
schemas=suite.get('schemas'),
258+
tools=suite.get('tools'),
259+
partial_resolver=partial_resolver_fn if resolver_partials else None,
260+
)
261+
262+
# Define partials if they exist.
263+
partials: dict[str, str] = suite.get('partials', {})
264+
for name, template in partials.items():
265+
dotprompt.define_partial(name, template)
266+
267+
return dotprompt
225268

226269
async def run_yaml_test(
227270
self,
@@ -241,18 +284,17 @@ async def run_yaml_test(
241284
Returns:
242285
None
243286
"""
244-
logger.debug(
245-
'Running spec test',
246-
yaml_file=yaml_file,
247-
suite=suite,
248-
test=test_case,
287+
suite_name = suite.get('name')
288+
test_name = test_case.get('desc')
289+
logger.info(
290+
f'[TEST] \033[1m{yaml_file.name}\033[0m: {suite_name} > {test_name}',
291+
yaml_file=yaml_file.name,
292+
suite_name=suite_name,
293+
test_name=test_name,
294+
# suite=suite,
295+
# test=test_case,
249296
)
250297

251-
# Define partials if they exist.
252-
partials: dict[str, str] = suite.get('partials', {})
253-
for name, template in partials.items():
254-
dotprompt.define_partial(name, template)
255-
256298
# TODO: Render the template.
257299
# data = {**suite.get('data', {}), **test_case.get('data', {})}
258300
# result = await dotprompt.render(
@@ -267,13 +309,15 @@ async def run_yaml_test(
267309
# TODO: Render the metadata.
268310
# TODO: Compare pruned metadata to the expected output.
269311

270-
logger.debug(
271-
'Finished running spec test',
272-
yaml_file=yaml_file,
273-
suite=suite,
274-
test=test_case,
275-
# result=result,
276-
)
312+
# logger.info(
313+
# f'[TEST] \033[1m{yaml_file.name}\033[0m: {suite_name} > {test_name} finished',
314+
# yaml_file=yaml_file.name,
315+
# suite_name=suite_name,
316+
# test_name=test_name,
317+
# # suite=suite,
318+
# # test=test_case,
319+
# # result=result,
320+
# )
277321

278322

279323
if __name__ == '__main__':

python/handlebarrz/src/handlebarrz/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ def format_date(params, hash, ctx):
414414
try:
415415
# TODO: Fix this type error.
416416
self._template.register_helper(name, create_helper(helper_fn)) # type: ignore[arg-type]
417-
logger.debug({'event': 'helper_registered', 'name': name})
417+
# logger.debug({'event': 'helper_registered', 'name': name})
418418
except Exception as e:
419419
logger.exception({
420420
'event': 'helper_registration_error',

0 commit comments

Comments
 (0)