fix: fail loudly on an unknown workflow expression filter#3074
Conversation
The expression evaluator's filter dispatch fell through to `return value`
for any unregistered filter, so a typo'd or unsupported filter such as
`{{ items | length }}` rendered the value unchanged with no error and the
run completed — a silent wrong result.
Raise a clear ValueError instead, naming the offending filter and the valid
ones, mirroring the strict handling already used for `from_json`. The five
registered filters (default/join/map/contains/from_json) are unchanged; the
`name(arg)` form of an unknown filter is now caught too.
There was a problem hiding this comment.
Pull request overview
This PR updates the workflow expression evaluator to fail loudly when encountering an unknown (or otherwise unhandled) pipe filter, preventing silent passthrough that can hide typos and produce incorrect workflow results.
Changes:
- Change filter dispatch in
evaluate_expressionto raiseValueErrorinstead of silently returning the unfiltered value for unrecognized filters. - Add tests asserting that unknown filters raise (both
| nameand| name(arg)forms). - Add a regression test intended to ensure registered filters keep working.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/workflows/expressions.py |
Replaces the silent unknown-filter fallback with a ValueError that includes the offending filter and expected forms. |
tests/test_workflows.py |
Adds coverage for unknown-filter failures and a regression test for existing filter behavior. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 2
…er map
Address the review feedback on the unknown-filter fail-loud path:
- A *registered* filter used in an unsupported form (e.g. `| join` or
`| map` with no argument) raised the misleading "unknown filter
'<name>'" — the filter is registered, the syntax isn't. It now raises
a message naming it as a known filter misused. A new
`_REGISTERED_FILTERS` constant drives the distinction.
- `test_registered_filters_unaffected` now also exercises `map('attr')`,
which it previously claimed to cover but didn't. Add
`test_registered_filter_unsupported_form_raises` to pin the new path.
|
@mnriem — pushed
The five registered filters are unchanged; full suite 3913 passed, 59 skipped, |
Copilot review: the hint listed default('x') but omitted the valid
no-argument default form (| default), which this module supports.
|
@mnriem pushed |
|
Thank you! |
Description
Fixes #3073.
The expression evaluator's filter dispatch fell through to
return valuefor any unregistered filter, so a typo'd or unsupported filter — e.g.{{ items | length }}— rendered the value unchanged with no error, and the run completed. A silent wrong result, with nothing pointing at the cause.This makes the unknown-filter path fail loudly: an unrecognized filter raises a clear
ValueErrornaming the offending filter and the valid ones, reusing the same strict-ValueErrorstyle already used forfrom_json(which raises on its mis-wired forms precisely to avoid "silently falling through to the unknown-filter path and returning the unparsed value"). It closes that path in general, consistent with the fail-loud direction of #2957 (fan-out items) and #2961 (from_json).default/join/map/contains/from_json) are unchanged — no behavior change for any valid usage.| length) and thename(arg)form (| upper('x')), which previously also fell through silently.Backward-compat note (calling it out honestly): this turns a previously silent author error into a loud one. Any workflow that today relies on an unknown filter being silently dropped would now error. That passthrough was never intentional (the
from_jsonstrictness comment already names it as a path to avoid), and a scan of the repo's own workflow/template YAMLs found no{{ … | <unknown-filter> }}usage, and the full suite stays green — but flagging it so you can weigh the direction. Happy to rework as a warning-instead-of-raise, or gate it, if you'd prefer.Testing
uv run specify --helpuv sync && uv run pytest— full suite 3913 passed, 59 skipped{{ [1,2,3] | length }}: pre-fix it printedcount=[1, 2, 3]and completed; post-fix the run fails loudly withunknown filter 'length'.Tests in
TestExpressions(tests/test_workflows.py):test_filter_unknown_name_raises—| lengthraisesValueError.test_filter_unknown_name_with_args_raises—| upper('x')raises (thename(arg)form).test_registered_filter_unsupported_form_raises— a registered filter in an unsupported form (| join,| mapwith no arg) raises a misused-filter message, not "unknown filter".test_registered_filters_unaffected— regression: all five registered filters still work, now includingmap('attr').The two unknown-filter tests are red against
main(DID NOT RAISE), green with the fix.AI Disclosure
This change was authored with Claude Code: the AI drafted the fix and the tests and ran the suite. I discovered the behavior through end-to-end workflow-expression testing, verified the root cause and the red-against-main / green-with-fix tests myself, and reviewed the diff. The fix deliberately reuses the existing
from_jsonerror style rather than inventing a new one.