Skip to content

feat(workflows): add from_json expression filter#2961

Open
doquanghuy wants to merge 1 commit into
github:mainfrom
doquanghuy:feat/2960-from-json-filter
Open

feat(workflows): add from_json expression filter#2961
doquanghuy wants to merge 1 commit into
github:mainfrom
doquanghuy:feat/2960-from-json-filter

Conversation

@doquanghuy

Copy link
Copy Markdown
Contributor

Description

Fixes #2960.

Adds one arg-less pipe filter to the sandboxed expression evaluator:

items: "{{ steps.emit.output.stdout | from_json }}"

_filter_from_json parses a JSON string into its typed value (list/dict/scalar). Semantics are parse-or-raise: invalid JSON or non-string input raises a clear ValueError — never a silent passthrough, since a parse failure means the pipeline wiring is wrong and silence would hide it. The module docstring's filter list is updated; no other filter or evaluator behavior is touched.

This is the smallest unblock for feeding fan-out items: (or any condition/arg) from a step that computes a collection at runtime. It composes with — and doesn't preclude — a future declared-outputs: mechanism, which I'm proposing separately.

Testing

  • Ran existing tests with uv sync && uv run pytesttests/test_workflows.py 210 passed
  • Three new tests in TestExpressions (valid JSON → typed value; invalid JSON raises; non-string raises) — all three are red against current main, green with the filter (verified both directions)
  • uvx ruff check src/ — clean
  • Tested locally with uv run specify --help
  • Tested with a sample project (covered by the evaluator tests; exercised manually with the [Bug]: fan-out silently coerces a non-list 'items' to [] — zero-instance runs with no error #2956 repro workflow rewired through | from_json)

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (describe below)

Code, tests, and this description were authored with AI assistance (Claude); verified by running the repo's test suite and ruff locally in both red and green directions.

Step outputs captured as strings could never become typed values in
templates - the filter set was default/join/map/contains only, so e.g.
a fan-out items: could never consume a step's JSON stdout. Add an
arg-less from_json pipe filter with parse-or-raise semantics: invalid
JSON or non-string input raises a clear ValueError rather than passing
through silently.

Fixes github#2960
@doquanghuy doquanghuy requested a review from mnriem as a code owner June 12, 2026 17:29
@doquanghuy

Copy link
Copy Markdown
Contributor Author

@mnriem when you have a moment, would appreciate a review — happy to adjust anything.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new from_json pipe filter to the workflow expression evaluator so step outputs (typically strings like stdout) can be converted into typed Python values (dict/list/scalar) inside templates, enabling patterns like feeding fan-out.items from a runtime-emitted JSON string.

Changes:

  • Implement from_json filter in the sandboxed expression evaluator (json.loads with strict ValueError semantics on misuse).
  • Extend filter documentation in the evaluator docstring to include from_json.
  • Add unit tests validating successful parsing and failure modes (invalid JSON, non-string input).
Show a summary per file
File Description
src/specify_cli/workflows/expressions.py Adds the from_json filter and wires it into filter dispatch.
tests/test_workflows.py Adds 3 tests covering from_json success + error cases.

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

Comment on lines 175 to +179
filter_name = filter_expr.strip()
if filter_name == "default":
return _filter_default(value)
if filter_name == "from_json":
return _filter_from_json(value)
Comment thread tests/test_workflows.py
ctx = StepContext(inputs={"text": "hello world"})
assert evaluate_expression("{{ inputs.text | contains('world') }}", ctx) is True

def test_filter_from_json_parses_list(self):
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.

[Feature]: a from_json expression filter so step outputs can become typed values

3 participants