Skip to content

Bump pydantic-handlebars to 0.2.1 and drop escape-coupling shims#1963

Merged
dmontagu merged 1 commit into
feature/variable-composition-tier1-fixesfrom
feature/variable-composition-handlebars-021
May 25, 2026
Merged

Bump pydantic-handlebars to 0.2.1 and drop escape-coupling shims#1963
dmontagu merged 1 commit into
feature/variable-composition-tier1-fixesfrom
feature/variable-composition-handlebars-021

Conversation

@dmontagu
Copy link
Copy Markdown
Contributor

Stacked on #1962. Bumps the dep and cleans up the now-stale escape shims that #1962 left a coupling comment about.

Why now

pydantic-handlebars 0.2.1 ships spec-compliant escape semantics: a run of N backslashes preceding an open delimiter contributes N // 2 literal \ characters, with parity deciding whether the mustache renders (even) or stays literal (odd).

That makes the logfire-side _HAS_REFERENCE regex ((?<!\\)@\{ — "any preceding backslash means escaped") actively wrong for even-backslash cases. With 0.2.1:

Source Old logfire behaviour Correct (post-0.2.1)
\@{x}@ literal @{x}@ literal @{x}@
\\@{x}@ literal \@{x}@ \X
\\\@{x}@ literal \\@{x}@ literal \@{x}@
\\\\@{x}@ literal \\\@{x}@ \\X

The regex returned has_references == False for any \@{ prefix, so even-backslash cases short-circuited the composition path and emerged unrendered. The fix routes everything containing @{ through the renderer, which now handles parity correctly.

What changed

  • pyproject.toml: pydantic-handlebars>=0.2.0>=0.2.1 in both the [variables] extra and the dev group.
  • composition.py cleanup:
    • has_references drops the regex; becomes '@{' in serialized_value. Doing parity-correct detection in pure regex would need a variable-width lookbehind we can't write portably in re on 3.10/3.11; with the renderer as the source of truth it isn't needed.
    • _render_value no longer does the manual \@{@{ substitution for "no refs" strings. Strings with no @{ pass through unchanged; strings with @{ route through the renderer.
    • expand_references drops the special-case early return that called _unescape_serialized when no real refs resolved. _render_value handles escape-only strings on its own walk.
    • _unescape_serialized is unused and deleted.
    • _HAS_REFERENCE regex deleted (with the coupling-note comment).

Tests

Two pre-existing tests asserted the old (buggy by spec) behaviour and have been updated to assert the correct behaviour:

  • test_keyword_block_references_are_ignored@{#if this}@yes@{/if}@ now renders yes (real Handlebars if block against the empty context, which is truthy). The composed == [] assertion still pins the property that this and if aren't resolved as variable lookups.
  • test_referenced_escaped_reference_is_preserved\@{not_a_ref}@ now renders as @{not_a_ref}@ (escape consumed per spec).

New test test_backslash_run_parity_under_composition pins the parity table (N = 1, 2, 3, 4) end-to-end through Variable.get() so the previously-buggy even-backslash cases stay correct.

447 variable / composition / template / push tests pass. Pyright + ruff format + check clean.

Merge ordering

Targets feature/variable-composition-tier1-fixes (#1962's branch). Once #1962 merges → #1954 → main, this rebases onto main.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@dmontagu dmontagu force-pushed the feature/variable-composition-handlebars-021 branch 4 times, most recently from eecfd4a to 3295ede Compare May 25, 2026 02:17
`pydantic-handlebars 0.2.1` matches the Handlebars.js spec for runs of
backslashes preceding an open delimiter: N backslashes contributes
`N // 2` literal `\` characters, with parity deciding whether the
mustache renders (even) or stays literal (odd). That made the logfire
`_HAS_REFERENCE` regex (`(?<!\\)@\{` — "any preceding backslash means
escaped") actively wrong for even-backslash cases like `\\@{x}@`: we
returned `has_references == False`, took the short-circuit path, and
ended up with literal `\@{x}@` in output instead of `\X`.

Fix is also a cleanup. The renderer is now the single source of truth
for escape semantics, so the regex gate can degrade to a plain
substring check (`'@{' in v`):

- `has_references` is now `_HAS_OPEN_DELIM in serialized_value`. No
  more escape detection in the regex layer. Variable-width lookbehind
  would have been required to do this correctly in `re`, and is
  unnecessary now.
- `_render_value` no longer does the manual `\@{` → `@{` substitution
  when "no refs" — strings with no `@{` at all pass through unchanged;
  strings with `@{` (escaped or not) route through the renderer, which
  handles parity-correct unescape.
- `expand_references` drops the early-return-with-`_unescape_serialized`
  branch. Even when no real refs resolve, `_render_value` still walks
  the structure so escape-only strings get parity-correct handling.
- `_unescape_serialized` is unused and deleted.

## Tests

- Updated two pre-existing tests that asserted the *old* (buggy by spec)
  behaviour:
  - `test_keyword_block_references_are_ignored` now expects
    `@{#if this}@yES@{/if}@` to render `yes` (real Handlebars `if` block
    against the empty context, which is truthy). `composed == []` still
    asserts that no variable lookups are performed.
  - `test_referenced_escaped_reference_is_preserved` (renamed in spirit)
    now expects `\@{not_a_ref}@` to render as `@{not_a_ref}@` — the
    backslash is the escape marker per the spec.
- New `test_backslash_run_parity_under_composition` covers the
  parity table (1, 2, 3, 4 backslashes) end-to-end through
  `Variable.get()` so the previously-buggy even-backslash cases are
  pinned.

447 variable / composition / template / push tests pass.
@dmontagu dmontagu force-pushed the feature/variable-composition-handlebars-021 branch from 3295ede to 7e856d7 Compare May 25, 2026 02:47
@dmontagu dmontagu merged commit 221bda9 into feature/variable-composition-tier1-fixes May 25, 2026
14 checks passed
@dmontagu dmontagu deleted the feature/variable-composition-handlebars-021 branch May 25, 2026 02:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant