Skip to content

fix(template): support boolean argument in Jinja default filter#292

Merged
jrob5756 merged 3 commits into
microsoft:mainfrom
hertznsk:fix/jinja-default-filter
Jul 13, 2026
Merged

fix(template): support boolean argument in Jinja default filter#292
jrob5756 merged 3 commits into
microsoft:mainfrom
hertznsk:fix/jinja-default-filter

Conversation

@hertznsk

Copy link
Copy Markdown
Contributor

Summary

Fixes #288.

Conductor overrides Jinja2's built-in default filter with a custom implementation that previously only accepted two parameters. Valid Jinja templates using the standard third boolean argument raised TypeError: _default_filter() takes from 1 to 2 positional arguments but 3 were given.

This PR makes TemplateRenderer._default_filter signature-compatible with Jinja's built-in default(value, default_value="", boolean=False).

What changed

src/conductor/executor/template.py_default_filter now accepts boolean: bool = False:

  • boolean=False (default): preserves existing Conductor behavior — only None triggers the default; empty strings, 0, False, [], {} are returned as-is.
  • boolean=True: standard Jinja falsey semantics — any falsey value (None, "", 0, False, [], {}) returns the default; truthy values pass through unchanged.

The parameter name default is kept (not renamed to default_value) to avoid breaking any existing keyword-argument callers.

Tests

tests/test_executor/test_template.py — 14 new tests under TestTemplateRendererDefaultFilter:

  • boolean=True with each falsey value (None, "", 0, False, [], {}) → default
  • boolean=True with a truthy value → value returned unchanged
  • Two-arg form regression: None → default; "", 0, False, [], {} → returned as-is
  • Explicit boolean=False regression for the same edge cases

Verification

  • uv run pytest tests/test_executor/test_template.py -v60/60 passed
  • make check (ruff check + ruff format + ty) → exit 0
  • Manual: uv run python -c 'from conductor.executor.template import TemplateRenderer; r = TemplateRenderer(); print(r.render("{{ value | default(\\"x\\", true) }}", {"value": None}))' → prints x without TypeError

Out of scope

Per the issue's suggested direction, item 1 ("Return default_value for Jinja Undefined values") is intentionally not changed here: Conductor runs with StrictUndefined, so undefined variables raise TemplateError before reaching the filter — the existing fail-fast behavior is preserved.

@hertznsk hertznsk force-pushed the fix/jinja-default-filter branch from bbc45fb to d50d7ff Compare July 13, 2026 09:53
Conductor overrides Jinja's built-in `default` filter with a two-argument
implementation, so templates using the standard third `boolean` argument
failed with TypeError: _default_filter() takes from 1 to 2 positional
arguments but 3 were given.

Changes:
- _default_filter now accepts boolean: bool = False
- boolean=False (default): preserves current Conductor behavior —
  only None maps to the default; "", 0, False, [], {} pass through as-is
- boolean=True: standard Jinja falsey semantics — any falsey value maps
  to the default; truthy values pass through unchanged
- The `default` parameter name is kept (not renamed to `default_value`)
  so existing callers passing it as a keyword argument keep working

Tests (14 new under TestTemplateRendererDefaultFilter):
- boolean=True with each falsey value returns the default
- boolean=True with a truthy value returns the value unchanged
- Two-argument form: None maps to default; "", 0, False, [], {} pass through
- Explicit boolean=False for the same edge cases

Out of scope: Jinja Undefined handling is unchanged — Conductor runs with
StrictUndefined, so undefined variables raise TemplateError before they
ever reach the filter (fail-fast preserved).

Fixes microsoft#288
@hertznsk hertznsk force-pushed the fix/jinja-default-filter branch from d50d7ff to 005b606 Compare July 13, 2026 13:27
@hertznsk hertznsk changed the title fix(template): поддержка boolean-аргумента в default-фильтре Jinja fix(template): support boolean argument in Jinja default filter Jul 13, 2026

@jrob5756 jrob5756 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good change. Some small things, the new argument works for defined falsey values, but the missing-variable path still differs from Jinja and needs a regression test. I also left a small docstring clarification.

If you can wrap these up, we can merge! Thanks again.

Comment thread src/conductor/executor/template.py Outdated
Comment on lines 94 to 100
if boolean:
if not value:
return default
else:
if value is None:
return default
return value

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This still breaks the missing-value case from #288. With StrictUndefined, not value calls StrictUndefined.__bool__ and raises, while Jinja's built-in default returns the fallback. Please check Undefined before testing truthiness and add coverage for a missing variable in both forms. This replacement also needs Undefined imported from jinja2.

Suggested change
if boolean:
if not value:
return default
else:
if value is None:
return default
return value
if isinstance(value, Undefined):
return default
if boolean and not value:
return default
if value is None:
return default
return value

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 0ab6c6c. Undefined is now imported from jinja2 and checked first, before any truthiness test, so StrictUndefined.__bool__ is never reached:

if isinstance(value, Undefined):
    return default
if boolean and not value:
    return default
if value is None:
    return default
return value

Both {{ v | default('x') }} and {{ v | default('x', true) }} now return the fallback for a missing variable, while unguarded {{ missing }} still raises under StrictUndefined (fail-fast preserved). Added regression tests for a missing top-level variable in both the two-arg and boolean=True forms (including the positional true shorthand), plus a missing nested dict key.

Comment thread src/conductor/executor/template.py Outdated
Args:
value: The value to check.
default: Default value to return if value is None.
default: Default value to return if value is None or falsey.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This reads as though every falsey value uses the fallback. Empty strings, zero, False, and empty collections are preserved unless boolean=True.

Suggested change
default: Default value to return if value is None or falsey.
default: Value returned for None, or for any falsey value when boolean=True.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reworded in 0ab6c6c to make clear that empty strings, zero, False, and empty collections are preserved unless boolean=True:

default: Value returned for None, Undefined, or for any falsey value
    when boolean=True.

I used your suggested phrasing and added Undefined since the filter now also returns the fallback for undefined values.

Genadij Blinov and others added 2 commits July 13, 2026 20:57
Under StrictUndefined, a missing variable reaches the custom `default`
filter as a StrictUndefined object. The boolean=True path then called
StrictUndefined.__bool__ and raised instead of returning the fallback,
so both `{{ v | default('x') }}` and `{{ v | default('x', true) }}`
raised for undefined variables. This broke templates that use the
documented Jinja calling convention to guard optional values.

Check isinstance(value, Undefined) first so any undefined value returns
the fallback, matching Jinja's built-in default() semantics while keeping
StrictUndefined fail-fast for unguarded expressions. Reword the default
param docstring to describe the actual behavior (fallback for None,
Undefined, or any falsey value when boolean=True). Also correct a stale
comment in workflow.py that claimed default() cannot catch None without
boolean=true.

Add regression tests covering a missing variable in both the two-arg and
boolean=True forms (including the positional true shorthand) and a missing
nested dict key.

Addresses review feedback on microsoft#292. Fixes microsoft#288.
# Conflicts:
#	src/conductor/executor/template.py

@jrob5756 jrob5756 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm. approved

@jrob5756 jrob5756 merged commit 8443d80 into microsoft:main Jul 13, 2026
9 checks passed
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.

Custom default filter breaks Jinja's default(value, default_value, boolean) calling convention

2 participants