From 5ba317613e983756d38e99ac5486d019da5998c3 Mon Sep 17 00:00:00 2001 From: jawwad-ali Date: Wed, 22 Jul 2026 10:32:51 +0500 Subject: [PATCH] fix(workflows): list-literal expression ignores trailing/empty commas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A workflow list-literal expression with a trailing (or leading/double) comma — '{{ [1, 2,] }}' — evaluated to [1, 2, None]: _split_top_level_commas returns a trailing empty segment, which _evaluate_simple_expression resolves as an empty dot-path to None. That silently widens membership tests and renders a stray None in joins. Python and Jinja2 both tolerate trailing commas. Drop whitespace-empty segments from the comprehension. An intentional empty-string element ('') survives because its segment strips to "''" (truthy), so ['', 'a'] is preserved. Completes the quoted-comma handling from #3134. Test: [1, 2,] and [1,, 2] -> [1, 2]; ['', 'a'] -> ['', 'a'] (fails before: trailing None). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/specify_cli/workflows/expressions.py | 4 ++++ tests/test_workflows.py | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/specify_cli/workflows/expressions.py b/src/specify_cli/workflows/expressions.py index 0c70a7b284..1805b4bc02 100644 --- a/src/specify_cli/workflows/expressions.py +++ b/src/specify_cli/workflows/expressions.py @@ -535,6 +535,10 @@ def _evaluate_simple_expression(expr: str, namespace: dict[str, Any]) -> Any: items = [ _evaluate_simple_expression(i.strip(), namespace) for i in _split_top_level_commas(inner) + # Drop empty segments from trailing/leading/double commas ([1, 2,] -> + # [1, 2], not [1, 2, None]). An intentional empty-string element + # ('') strips to "''" (truthy), so ['', 'a'] is preserved. + if i.strip() ] return items diff --git a/tests/test_workflows.py b/tests/test_workflows.py index cd1b235411..4272795666 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -404,6 +404,17 @@ def test_list_literal_preserves_quoted_commas(self): assert evaluate_expression('{{ [["a", "b"], "c"] }}', ctx) == [["a", "b"], "c"] assert evaluate_expression("{{ [[1, 2], [3, 4]] }}", ctx) == [[1, 2], [3, 4]] + def test_list_literal_ignores_trailing_and_empty_commas(self): + from specify_cli.workflows.expressions import evaluate_expression + from specify_cli.workflows.base import StepContext + + ctx = StepContext() + # A trailing comma must not append a spurious None element. + assert evaluate_expression("{{ [1, 2,] }}", ctx) == [1, 2] + assert evaluate_expression("{{ [1,, 2] }}", ctx) == [1, 2] + # …but an intentional empty-string element is still preserved. + assert evaluate_expression("{{ ['', 'a'] }}", ctx) == ["", "a"] + def test_operator_splitting_is_quote_aware(self): from specify_cli.workflows.expressions import ( evaluate_condition,