Skip to content

fix(iohandler): don't drop empty list/dict/tuple args to keep.* functions - #6729

Open
Prabal864 wants to merge 1 commit into
keephq:mainfrom
Prabal864:fix-6728-iohandler-empty-arg-dropped
Open

fix(iohandler): don't drop empty list/dict/tuple args to keep.* functions#6729
Prabal864 wants to merge 1 commit into
keephq:mainfrom
Prabal864:fix-6728-iohandler-empty-arg-dropped

Conversation

@Prabal864

Copy link
Copy Markdown
Contributor

What

Fixes #6728.

IOHandler's keep.* template-function-call parser built its positional-argument list with:

if (
    _arg
    or _arg == ""
    or (_arg == 0 or _arg == 0.0)
    and _arg is not False
):
    _args.append(_arg)

The comment above it says this exists so "" and 0/0.0 aren't dropped as "empty" — but it only special-cases those two:

  • A literal []/{}/() argument (parsed a few lines above via ast.literal_eval into a real, empty container) is falsy, not == "", and not == 0/0.0, so it fails every branch and gets silently dropped.
  • A real (non-string) False — which can come back from a nested keep.* call — also gets dropped: False == 0 is True in Python, so (_arg == 0 or _arg == 0.0) is True, but the trailing and _arg is not False then makes the whole condition False.

Dropping an argument shifts every subsequent positional argument one slot left, either raising a confusing TypeError deep inside keep.functions, or silently binding the wrong value to the wrong parameter.

Fix

_arg is initialized to None and every parsing branch always reassigns it to a real value — it only stays None when nothing was parsed (e.g. a nested keep.* call that returned nothing). So the guard can simply be if _arg is not None:, which keeps everything the old guard kept ("", 0, 0.0) plus [], {}, (), and False, and only drops the genuine no-value case.

Testing

Added 3 cases to tests/test_iohandler.py::test_with_function_empty_container_argument, exercised through the real IOHandler.render():

  • keep.join([], '-')"" (previously the [] was dropped, leaving join('-'), which treats '-' as the iterable string and fails to parse it as JSON)
  • keep.join({}, '-')""
  • keep.len([])0

Same environment limitation as my last two PRs here (#6720, #6726): poetry install fails on this platform because uvloop doesn't support Windows, so I couldn't run the full test suite. What I did verify:

  • python -m py_compile passes on both changed files.
  • black --check and isort --check report no new issues introduced by this diff (I confirmed the two pre-existing formatting complaints elsewhere in iohandler.py and test_iohandler.py are unrelated to and untouched by this change).
  • Extracted the exact guard, copied verbatim, and ran it standalone against real ast-parsed [], {}, (), and non-empty-list arguments — confirmed the empty containers vanish under the old guard and are preserved under the new one, with no change to the already-correct cases.

A maintainer running this on Linux/macOS should see tests/test_iohandler.py::test_with_function_empty_container_argument pass outright.

Checklist

  • I have read the Contributing Guide
  • If you've added code that should be tested, add tests.
  • If you've changed APIs, update the documentation. — not applicable, no API change.
  • Ensure the test suite passes.
  • Make sure your code lints (black/isort) — for the lines this PR actually touches.

…ions

The positional-argument guard in IOHandler's keep.* template-function
parser was patched to special-case "" and 0/0.0 (per its own comment)
but never handled empty containers: a literal [] or {} argument to a
keep.* function (e.g. keep.join([], "-")) parses to a real, falsy-but-
legitimate empty list/dict via ast.literal_eval, fails every branch of
the old truthy-based guard, and gets silently dropped from the call -
shifting every subsequent positional argument one slot to the left.
The same guard also drops a real (non-string) False value, since
False == 0 is True in Python but the guard's `and _arg is not False`
clause then makes the whole condition false.

_arg only ever stays at its None sentinel default when none of the
parsing branches produced a value (e.g. a nested keep.* call that
itself returned nothing), so replacing the guard with a plain
`if _arg is not None:` is a strict improvement: it keeps everything
the old guard kept ("", 0, 0.0) plus [], {}, (), and False, and only
drops the genuine no-value case.

Closes keephq#6728
@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. Bug Something isn't working labels Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something isn't working size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[🐛 Bug]: IOHandler silently drops empty list/dict/tuple (and literal False) arguments to keep.* template functions

1 participant