You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
IOHandler's template-function-call parser (keep/iohandler/iohandler.py, inside _parse_token's nested _parse()) builds the positional-argument list for a keep.* function call with this guard:
# keep/iohandler/iohandler.py, lines 334-343# if the value is empty '', we still need to pass it to the function# also, if the value is 0 or 0.0, we need to pass it to the function# 0 == False, so we need to check if the value is not False explicitlyif (
_argor_arg==""or (_arg==0or_arg==0.0)
and_argisnotFalse
):
_args.append(_arg)
The comment says this exists specifically so "" and 0/0.0 aren't dropped as "empty" - but the guard only special-cases those two, and misses:
Empty containers. A literal [], {}, or () argument (parsed a few lines above via ast.literal_eval/astunparse.unparse, producing a real empty list/dict/tuple object) is falsy, not == "", and not == 0/0.0 - so it's silently dropped from _args instead of being passed to the function.
Literal False.False == 0 is True in Python, so (_arg == 0 or _arg == 0.0) is True for _arg is False too - but the guard then requires and _arg is not False, which evaluates to False, so the whole condition is False and a literal False argument is also silently dropped.
To Reproduce
The exact guard, copied verbatim, exercised against the real ast-based parsing used for []/{}/()/False template-function arguments:
f([], 'x') -> ['x'] # BUG: the [] vanished
f({}, 'x') -> ['x'] # BUG: the {} vanished
f((), 'x') -> ['x'] # BUG: the () vanished
f([1, 2], 'x') -> [[1, 2], 'x'] # correct: non-empty list preserved
Expected behavior
Any argument that was actually parsed to a real value - including [], {}, (), and False - should be passed through to the function positionally. Only a genuinely-unparseable/no-value case should be dropped.
Failure scenario: any workflow YAML step calling a keep.* template function where one positional argument is a field/step-output that legitimately renders to an empty list or dict (or the literal False), e.g. a literal {{ keep.some_function([], "fallback") }}, or a variable that resolves to []/{}/False at runtime. The empty/False argument silently vanishes, which shifts every subsequent positional argument one slot to the left - either raising a confusing TypeError about missing/extra arguments deep inside keep.functions, or (worse) silently binding the wrong value to the wrong parameter with no error at all.
Small, safe fix: since _arg is only ever left as its None sentinel default when none of the parsing branches produced a value (e.g. a nested keep.* call that itself returned nothing), the guard can simply become if _arg is not None: - which correctly keeps "", 0, 0.0, False, [], {}, and (), and only drops the genuine "nothing was parsed" case. Happy to send a PR for this.
Describe the bug
IOHandler's template-function-call parser (keep/iohandler/iohandler.py, inside_parse_token's nested_parse()) builds the positional-argument list for akeep.*function call with this guard:The comment says this exists specifically so
""and0/0.0aren't dropped as "empty" - but the guard only special-cases those two, and misses:[],{}, or()argument (parsed a few lines above viaast.literal_eval/astunparse.unparse, producing a real emptylist/dict/tupleobject) is falsy, not== "", and not== 0/0.0- so it's silently dropped from_argsinstead of being passed to the function.False.False == 0isTruein Python, so(_arg == 0 or _arg == 0.0)isTruefor_arg is Falsetoo - but the guard then requiresand _arg is not False, which evaluates toFalse, so the whole condition isFalseand a literalFalseargument is also silently dropped.To Reproduce
The exact guard, copied verbatim, exercised against the real
ast-based parsing used for[]/{}/()/Falsetemplate-function arguments:Output:
Expected behavior
Any argument that was actually parsed to a real value - including
[],{},(), andFalse- should be passed through to the function positionally. Only a genuinely-unparseable/no-value case should be dropped.Additional context
keep/iohandler/iohandler.pyon currentmain(commit range including through PR fix(providers): don't crash on empty choices in grok #6710 /f35afdd).keep.*template function where one positional argument is a field/step-output that legitimately renders to an empty list or dict (or the literalFalse), e.g. a literal{{ keep.some_function([], "fallback") }}, or a variable that resolves to[]/{}/Falseat runtime. The empty/Falseargument silently vanishes, which shifts every subsequent positional argument one slot to the left - either raising a confusingTypeErrorabout missing/extra arguments deep insidekeep.functions, or (worse) silently binding the wrong value to the wrong parameter with no error at all._argis only ever left as itsNonesentinel default when none of the parsing branches produced a value (e.g. a nestedkeep.*call that itself returned nothing), the guard can simply becomeif _arg is not None:- which correctly keeps"",0,0.0,False,[],{}, and(), and only drops the genuine "nothing was parsed" case. Happy to send a PR for this.