dotprompt-handlebars 0.0.1.dev1__cp312-cp312-manylinux_2_33_aarch64.whl → 0.1.1__cp312-cp312-manylinux_2_33_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotprompt_handlebars
3
- Version: 0.0.1.dev1
3
+ Version: 0.1.1
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Programming Language :: Python :: 3.10
@@ -0,0 +1,8 @@
1
+ dotprompt_handlebars-0.1.1.dist-info/METADATA,sha256=xetUj3NI052dvFAy1UXVOA0dFWKFtDsj2Mdg28kiruA,1169
2
+ dotprompt_handlebars-0.1.1.dist-info/WHEEL,sha256=AB65alw64Me6t53a6SXTkmn8HmlUtkGRrmAow8OqOnM,109
3
+ dotprompt_handlebars-0.1.1.dist-info/licenses/LICENSE,sha256=bsvE5_qSn_2LH2G-haMvT_AoIeINhX6fvzZTlyq2xJY,11340
4
+ handlebarrz/_native.pyi,sha256=dfLbPXf2DYH-lxurBEUWk6hChe96uVQ2Ss3kOT4kPDw,2386
5
+ handlebarrz/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ handlebarrz/__init__.py,sha256=_wK1CTsv3NmXcQ20Jl1wvn_z19QNsHbGurMuXZcAjTI,23127
7
+ handlebarrz/_native.cpython-312-aarch64-linux-gnu.so,sha256=s8_z_yQOjYda4wyRFzFQ4m_DhsbL-qFIuxke9DgHymE,1535104
8
+ dotprompt_handlebars-0.1.1.dist-info/RECORD,,
handlebarrz/__init__.py CHANGED
@@ -67,11 +67,14 @@ result = handlebars.render('formatted', {'name': 'World'}) # "Hello WORLD!"
67
67
  ```
68
68
  """
69
69
 
70
+ from __future__ import annotations
71
+
70
72
  import json
73
+ import re
71
74
  import sys # noqa
72
75
  from collections.abc import Callable
73
76
  from pathlib import Path
74
- from typing import Any
77
+ from typing import Any, TypedDict
75
78
 
76
79
  import structlog
77
80
 
@@ -81,6 +84,7 @@ else: # noqa
81
84
  from enum import StrEnum # noqa
82
85
 
83
86
  from ._native import (
87
+ HandlebarrzHelperOptions,
84
88
  HandlebarrzTemplate,
85
89
  html_escape,
86
90
  no_escape,
@@ -89,8 +93,23 @@ from ._native import (
89
93
  logger = structlog.get_logger(__name__)
90
94
 
91
95
 
92
- HelperFn = Callable[[list[Any], dict[str, Any], dict[str, Any]], str]
93
- NativeHelperFn = Callable[[str, str, str], str]
96
+ HelperFn = Callable[[list[Any], 'HelperOptions'], str]
97
+ NativeHelperFn = Callable[[str, HandlebarrzHelperOptions], str]
98
+ Context = dict[str, Any]
99
+
100
+
101
+ class RuntimeOptions(TypedDict):
102
+ """Options for the runtime of a Handlebars template.
103
+
104
+ These options are used to configure the runtime behavior of a Handlebars
105
+ template. They can be passed to the compiled template function to customize
106
+ the rendering process.
107
+ """
108
+
109
+ data: dict[str, Any] | None
110
+
111
+
112
+ CompiledRenderer = Callable[[Context, RuntimeOptions | None], str]
94
113
 
95
114
 
96
115
  class EscapeFunction(StrEnum):
@@ -159,6 +178,7 @@ class Template:
159
178
  self._template.set_escape_fn(escape_fn)
160
179
  self._template.set_strict_mode(strict_mode)
161
180
  self._template.set_dev_mode(dev_mode)
181
+ self._known_partials: set[str] = set()
162
182
 
163
183
  @property
164
184
  def strict_mode(self) -> bool:
@@ -226,7 +246,7 @@ class Template:
226
246
  'function': escape_fn,
227
247
  })
228
248
  except ValueError as e:
229
- logger.error({'event': 'escape_function_error', 'error': str(e)})
249
+ logger.exception({'event': 'escape_function_error', 'error': str(e)})
230
250
  raise
231
251
 
232
252
  def register_template(self, name: str, template_string: str) -> None:
@@ -247,7 +267,7 @@ class Template:
247
267
  self._template.register_template(name, template_string)
248
268
  logger.debug({'event': 'template_registered', 'name': name})
249
269
  except ValueError as e:
250
- logger.error({
270
+ logger.exception({
251
271
  'event': 'template_registration_error',
252
272
  'name': name,
253
273
  'error': str(e),
@@ -270,15 +290,27 @@ class Template:
270
290
  """
271
291
  try:
272
292
  self._template.register_partial(name, template_string)
293
+ self._known_partials.add(name)
273
294
  logger.debug({'event': 'partial_registered', 'name': name})
274
- except ValueError as e:
275
- logger.error({
295
+ except Exception as e:
296
+ logger.exception({
276
297
  'event': 'partial_registration_error',
277
298
  'name': name,
278
299
  'error': str(e),
279
300
  })
280
301
  raise
281
302
 
303
+ def has_partial(self, name: str) -> bool:
304
+ """Check if a partial is registered.
305
+
306
+ Args:
307
+ name: The name of the partial to check.
308
+
309
+ Returns:
310
+ True if the partial is registered, False otherwise.
311
+ """
312
+ return name in self._known_partials
313
+
282
314
  def register_template_file(self, name: str, file_path: str | Path) -> None:
283
315
  """Register a template from a file.
284
316
 
@@ -303,7 +335,7 @@ class Template:
303
335
  'path': file_path_str,
304
336
  })
305
337
  except (FileNotFoundError, ValueError) as e:
306
- logger.error({
338
+ logger.exception({
307
339
  'event': 'template_file_registration_error',
308
340
  'name': name,
309
341
  'path': file_path_str,
@@ -335,7 +367,7 @@ class Template:
335
367
  'extension': extension,
336
368
  })
337
369
  except (FileNotFoundError, ValueError) as e:
338
- logger.error({
370
+ logger.exception({
339
371
  'event': 'templates_directory_registration_error',
340
372
  'path': dir_path_str,
341
373
  'extension': extension,
@@ -383,9 +415,9 @@ class Template:
383
415
  try:
384
416
  # TODO: Fix this type error.
385
417
  self._template.register_helper(name, create_helper(helper_fn)) # type: ignore[arg-type]
386
- logger.debug({'event': 'helper_registered', 'name': name})
418
+ # logger.debug({'event': 'helper_registered', 'name': name})
387
419
  except Exception as e:
388
- logger.error({
420
+ logger.exception({
389
421
  'event': 'helper_registration_error',
390
422
  'name': name,
391
423
  'error': str(e),
@@ -414,7 +446,7 @@ class Template:
414
446
  self._template.unregister_template(name)
415
447
  logger.debug({'event': 'template_unregistered', 'name': name})
416
448
 
417
- def render(self, name: str, data: dict[str, Any]) -> str:
449
+ def render(self, name: str, data: dict[str, Any], options: RuntimeOptions | None = None) -> str:
418
450
  """Render a template with the given data.
419
451
 
420
452
  Renders a previously registered template using the provided data
@@ -424,6 +456,7 @@ class Template:
424
456
  Args:
425
457
  name: The name of the template to render
426
458
  data: The data to render the template with
459
+ options: Additional options for the template.
427
460
 
428
461
  Returns:
429
462
  str: The rendered template string
@@ -432,19 +465,21 @@ class Template:
432
465
  ValueError: If the template does not exist or there is a rendering
433
466
  error.
434
467
  """
468
+ # TODO: options is currently ignored; need to add support for it.
469
+
435
470
  try:
436
471
  result = self._template.render(name, json.dumps(data))
437
472
  logger.debug({'event': 'template_rendered', 'name': name})
438
473
  return result
439
474
  except ValueError as e:
440
- logger.error({
475
+ logger.exception({
441
476
  'event': 'template_rendering_error',
442
477
  'name': name,
443
478
  'error': str(e),
444
479
  })
445
480
  raise
446
481
 
447
- def render_template(self, template_string: str, data: dict[str, Any]) -> str:
482
+ def render_template(self, template_string: str, data: dict[str, Any], options: RuntimeOptions | None = None) -> str:
448
483
  """Render a template string directly without registering it.
449
484
 
450
485
  Parses and renders the template string in one step. This is useful for
@@ -454,6 +489,7 @@ class Template:
454
489
  Args:
455
490
  template_string: The template string to render
456
491
  data: The data to render the template with
492
+ options: Additional options for the template.
457
493
 
458
494
  Returns:
459
495
  Rendered template string.
@@ -463,16 +499,79 @@ class Template:
463
499
  rendering error.
464
500
  """
465
501
  try:
502
+ # Merging runtime data with the data dictionary.
503
+ runtime_data = (options.get('data') if options is not None else {}) or {}
504
+ for k, v in runtime_data.items():
505
+ data[k] = v
506
+
507
+ # TODO: get rid of this once the Rust library has a local variables
508
+ # support.
509
+
510
+ # Local variables support workaround:
511
+ # Context: Handlebars Rust implementation currently doesn't support
512
+ # local variables (e.g. {{@my_variable}}), so instead we are
513
+ # dynamically replacing those with global variables
514
+ # (e.g. {{my_variable}}) to make things work.
515
+ # This is of course not an ideal solution, and it comes with some
516
+ # overhead, but so far we weren't able to detect a use case where
517
+ # this could be a blocker (but time will tell).
518
+ matches = re.findall(r'{{@.*?}}', template_string)
519
+ for m in set(matches):
520
+ key = m.strip('{}@').split('.')[0]
521
+ if key in runtime_data:
522
+ template_string = template_string.replace(m, m.replace('@', ''))
523
+
524
+ # Render the template.
466
525
  result = self._template.render_template(template_string, json.dumps(data))
467
526
  logger.debug({'event': 'template_string_rendered'})
468
527
  return result
469
528
  except ValueError as e:
470
- logger.error({
529
+ logger.exception({
471
530
  'event': 'template_string_rendering_error',
472
531
  'error': str(e),
473
532
  })
474
533
  raise
475
534
 
535
+ def compile(self, template_string: str) -> CompiledRenderer:
536
+ """Compile a template string into a reusable function.
537
+
538
+ This method provides an interface similar to Handlebars.js's `compile`.
539
+ It takes a template string and returns a function that can be called
540
+ with different data contexts to render the template.
541
+
542
+ Note: Unlike the JS version which can bake options into the compiled
543
+ template, this implementation returns a function that uses the current
544
+ configuration (strict mode, escape function, registered helpers, etc.)
545
+ of the `Template` instance at the time the returned function is called.
546
+
547
+ Args:
548
+ template_string: The Handlebars template string to compile.
549
+
550
+ Returns:
551
+ A callable function that takes a data dictionary and some runtime
552
+ options and returns the rendered string.
553
+
554
+ Raises:
555
+ ValueError: If there is a syntax error during the initial parse
556
+ check performed by render_template (depending on Rust implementation
557
+ details). Rendering errors will occur when the returned function is
558
+ called.
559
+ """
560
+
561
+ def compiled(context: Context, options: RuntimeOptions | None = None) -> str:
562
+ """Compiled template function.
563
+
564
+ Args:
565
+ context: The data to render the template with.
566
+ options: Additional options for the template.
567
+
568
+ Returns:
569
+ The rendered template string.
570
+ """
571
+ return self.render_template(template_string, context, options)
572
+
573
+ return compiled
574
+
476
575
  def register_extra_helpers(self) -> None:
477
576
  """Registers extra helper functions.
478
577
 
@@ -487,13 +586,45 @@ class Template:
487
586
  self._template.register_extra_helpers()
488
587
  logger.debug({'event': 'extra_helpers_registered'})
489
588
  except Exception as e:
490
- logger.error({
589
+ logger.exception({
491
590
  'event': 'extra_helpers_registration_error',
492
591
  'error': str(e),
493
592
  })
494
593
  raise
495
594
 
496
595
 
596
+ class HelperOptions:
597
+ """Handlebars helper options."""
598
+
599
+ def __init__(self, options: HandlebarrzHelperOptions) -> None:
600
+ self._options: HandlebarrzHelperOptions = options
601
+
602
+ def context(self) -> dict[str, Any]:
603
+ """Get a representation of a context."""
604
+ context_json = self._options.context_json()
605
+ return json.loads(context_json) or {}
606
+
607
+ def hash_value(self, key: str) -> Any:
608
+ """Get a hash value for the given key (resolved within the context).
609
+
610
+ Args:
611
+ key: The key corresponding for the hash required.
612
+
613
+ Returns:
614
+ The hash value.
615
+ """
616
+ hash_value_json = self._options.hash_value_json(key)
617
+ return json.loads(hash_value_json) if hash_value_json else ''
618
+
619
+ def fn(self) -> str:
620
+ """Renders a default inner template (if the helper is a block helper)."""
621
+ return self._options.template()
622
+
623
+ def inverse(self) -> str:
624
+ """Renders the template of the else branch (if any)."""
625
+ return self._options.inverse()
626
+
627
+
497
628
  def create_helper(
498
629
  fn: HelperFn,
499
630
  ) -> NativeHelperFn:
@@ -518,12 +649,9 @@ def create_helper(
518
649
  Function compatible with the Rust interface.
519
650
  """
520
651
 
521
- def wrapper(params_json: str, hash_json: str, ctx_json: str) -> str:
652
+ def wrapper(params_json: str, options: HandlebarrzHelperOptions) -> str:
522
653
  params = json.loads(params_json)
523
- hash = json.loads(hash_json)
524
- ctx = json.loads(ctx_json)
525
-
526
- result = fn(params, hash, ctx)
654
+ result = fn(params, HelperOptions(options))
527
655
  return result
528
656
 
529
657
  return wrapper
handlebarrz/_native.pyi CHANGED
@@ -17,10 +17,20 @@
17
17
  """Stub type annotations for native Handlebars."""
18
18
 
19
19
  from collections.abc import Callable
20
+ from typing import Any
20
21
 
21
22
  def html_escape(text: str) -> str: ...
22
23
  def no_escape(text: str) -> str: ...
23
24
 
25
+ class HandlebarrzHelperOptions:
26
+ """Stub type annotations for native Handlebars helper options."""
27
+
28
+ def __init__(self) -> None: ...
29
+ def context_json(self) -> str: ...
30
+ def hash_value_json(self, key: str) -> str: ...
31
+ def template(self) -> str: ...
32
+ def inverse(self) -> str: ...
33
+
24
34
  class HandlebarrzTemplate:
25
35
  """Stub type annotations for native Handlebars."""
26
36
 
@@ -1,8 +0,0 @@
1
- dotprompt_handlebars-0.0.1.dev1.dist-info/METADATA,sha256=9_xBpQnwIVm71CTHLHmtbtuCE--s2vPbFOXSwzTs6_E,1174
2
- dotprompt_handlebars-0.0.1.dev1.dist-info/WHEEL,sha256=AB65alw64Me6t53a6SXTkmn8HmlUtkGRrmAow8OqOnM,109
3
- dotprompt_handlebars-0.0.1.dev1.dist-info/licenses/LICENSE,sha256=bsvE5_qSn_2LH2G-haMvT_AoIeINhX6fvzZTlyq2xJY,11340
4
- handlebarrz/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- handlebarrz/__init__.py,sha256=pxIV7rtRAfnOlv6q3oPiNT1Srs2KR3CYrWtVAksDCKw,18124
6
- handlebarrz/_native.pyi,sha256=GhUc2bN9WHgl4OKBRLMjvPi4mg7FzVrXBsMpgajsiVo,2063
7
- handlebarrz/_native.cpython-312-aarch64-linux-gnu.so,sha256=sk3nPZ0hKrfnJ4R1-cKoxDwjOOSrOjuURxTS45AVKH4,1523752
8
- dotprompt_handlebars-0.0.1.dev1.dist-info/RECORD,,