fix(python): fan out only on JSON-array output, not any IEnumerable
The old code iterated every IEnumerable, so `output = "hello"` emitted one
item per character (a string is IEnumerable-of-char) and `output = {"a":1}`
emitted the keys as items. Now the script's output is serialized first and
the ValueKind decides: JSON array → per-element items; anything else →
single item. `output = None` remains an empty emission.
New tests cover string, dict, number, and None shapes.
This commit is contained in:
@@ -59,12 +59,19 @@ public sealed class PythonTransformNode : IActionNode
|
|||||||
string.IsNullOrEmpty(formatted) ? ex.Message : formatted);
|
string.IsNullOrEmpty(formatted) ? ex.Message : formatted);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scope.TryGetVariable("output", out object? outputObj))
|
if (!scope.TryGetVariable("output", out object? outputObj) || outputObj is null)
|
||||||
return Task.FromResult<IReadOnlyList<IReadOnlyList<Item>>>(new[] { Array.Empty<Item>() });
|
return Task.FromResult<IReadOnlyList<IReadOnlyList<Item>>>(new[] { Array.Empty<Item>() });
|
||||||
|
|
||||||
|
// Fan out only when `output` serializes to a JSON array. A naive
|
||||||
|
// `IEnumerable` check would treat a bare string as an iterable of chars
|
||||||
|
// (`output = "hello"` → 5 items) and a bare dict as an iterable of keys.
|
||||||
|
// Serializing first and dispatching on ValueKind gives one predictable rule.
|
||||||
|
var element = JsonToPython.ToJson(outputObj);
|
||||||
var outItems = new List<Item>();
|
var outItems = new List<Item>();
|
||||||
if (outputObj is System.Collections.IEnumerable ie)
|
if (element.ValueKind == JsonValueKind.Array)
|
||||||
foreach (var v in ie) outItems.Add(new Item(JsonToPython.ToJson(v)));
|
foreach (var el in element.EnumerateArray()) outItems.Add(new Item(el.Clone()));
|
||||||
|
else
|
||||||
|
outItems.Add(new Item(element));
|
||||||
|
|
||||||
return Task.FromResult<IReadOnlyList<IReadOnlyList<Item>>>(new[] { outItems });
|
return Task.FromResult<IReadOnlyList<IReadOnlyList<Item>>>(new[] { outItems });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,51 @@ public class PythonTransformNodeTests
|
|||||||
Assert.Contains("line 2", ex.Message);
|
Assert.Contains("line 2", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Bare_string_output_is_a_single_item_not_one_char_per_item()
|
||||||
|
{
|
||||||
|
var node = new PythonTransformNode();
|
||||||
|
var outs = await node.ExecuteAsync(Ctx("output = 'hello'"),
|
||||||
|
new IReadOnlyList<Item>[] { Array.Empty<Item>() }, CancellationToken.None);
|
||||||
|
Assert.Single(outs);
|
||||||
|
Assert.Single(outs[0]);
|
||||||
|
Assert.Equal("hello", outs[0][0].Json.GetString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Bare_dict_output_is_a_single_item_not_one_key_per_item()
|
||||||
|
{
|
||||||
|
var node = new PythonTransformNode();
|
||||||
|
var outs = await node.ExecuteAsync(Ctx("output = {'test': 'hi', 'n': 3}"),
|
||||||
|
new IReadOnlyList<Item>[] { Array.Empty<Item>() }, CancellationToken.None);
|
||||||
|
Assert.Single(outs);
|
||||||
|
Assert.Single(outs[0]);
|
||||||
|
Assert.Equal(JsonValueKind.Object, outs[0][0].Json.ValueKind);
|
||||||
|
Assert.Equal("hi", outs[0][0].Json.GetProperty("test").GetString());
|
||||||
|
Assert.Equal(3, outs[0][0].Json.GetProperty("n").GetInt32());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Bare_number_output_is_a_single_item()
|
||||||
|
{
|
||||||
|
var node = new PythonTransformNode();
|
||||||
|
var outs = await node.ExecuteAsync(Ctx("output = 42"),
|
||||||
|
new IReadOnlyList<Item>[] { Array.Empty<Item>() }, CancellationToken.None);
|
||||||
|
Assert.Single(outs);
|
||||||
|
Assert.Single(outs[0]);
|
||||||
|
Assert.Equal(42, outs[0][0].Json.GetInt64());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task None_output_yields_empty_list()
|
||||||
|
{
|
||||||
|
var node = new PythonTransformNode();
|
||||||
|
var outs = await node.ExecuteAsync(Ctx("output = None"),
|
||||||
|
new IReadOnlyList<Item>[] { Array.Empty<Item>() }, CancellationToken.None);
|
||||||
|
Assert.Single(outs);
|
||||||
|
Assert.Empty(outs[0]);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Syntax_error_message_contains_python_traceback()
|
public async Task Syntax_error_message_contains_python_traceback()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user