Skip to content

Commit 7167e85

Browse files
rustyconoverclaude
andcommitted
Raise error when table_in_out_function or scalar_function has empty input
Previously, passing an empty iterator to table_in_out_function() or scalar_function() would silently produce no output. Now raises ClientError with a helpful message suggesting to use table_function() for functions that generate data without input. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 594070f commit 7167e85

3 files changed

Lines changed: 44 additions & 15 deletions

File tree

tests/scalar/test_client.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,20 +116,20 @@ def test_empty_batch(self, example_worker: str) -> None:
116116
assert len(outputs) == 1
117117
assert outputs[0].num_rows == 0
118118

119-
def test_empty_iterator(self, example_worker: str) -> None:
120-
"""Test scalar function with no input batches."""
121-
with Client(example_worker) as client:
122-
outputs = list(
119+
def test_empty_iterator_raises(self, example_worker: str) -> None:
120+
"""Test scalar function with no input batches raises error."""
121+
with (
122+
Client(example_worker) as client,
123+
pytest.raises(ClientError, match="requires at least one input batch"),
124+
):
125+
list(
123126
client.scalar_function(
124127
function_name="double_column",
125128
input=iter([]),
126129
arguments=Arguments(positional=(pa.scalar("x"),)),
127130
)
128131
)
129132

130-
# No input means no output
131-
assert len(outputs) == 0
132-
133133
def test_scalar_function_not_started_raises(self, example_worker: str) -> None:
134134
"""Calling scalar_function before start should raise ClientError."""
135135
client = Client(example_worker)

tests/table_in_out/test_client.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@ def test_table_in_out_function_not_started_raises(
5858
class TestEdgeCases:
5959
"""Tests for edge cases and boundary conditions."""
6060

61+
def test_empty_iterator_raises(self, example_worker: str) -> None:
62+
"""Empty iterator (no batches) should raise ClientError."""
63+
with (
64+
Client(example_worker) as client,
65+
pytest.raises(ClientError, match="requires at least one input batch"),
66+
):
67+
list(
68+
client.table_in_out_function(
69+
function_name="echo",
70+
input=iter([]),
71+
)
72+
)
73+
6174
def test_empty_batch(self, example_worker: str) -> None:
6275
"""Empty batch (zero rows) should process correctly."""
6376
schema = make_schema(

vgi/client/client.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,7 +1446,7 @@ def table_in_out_function(
14461446
worker's registry.
14471447
input: Iterator yielding input RecordBatches. Must yield at least one
14481448
batch. The first batch's schema is used to initialize the IPC
1449-
stream. If the iterator is empty, no output is produced.
1449+
stream. Raises ClientError if the iterator is empty.
14501450
arguments: Optional Arguments container with positional and named
14511451
arguments to pass to the function. Defaults to empty Arguments().
14521452
bind_result_callback: Optional callback invoked with the raw bind
@@ -1468,9 +1468,10 @@ def table_in_out_function(
14681468
Final output from finalize() is always yielded last.
14691469
14701470
Raises:
1471-
ClientError: If the client is not started, input iterator yields
1472-
non-RecordBatch objects, communication with the worker fails,
1473-
or the worker returns an unexpected status or exception.
1471+
ClientError: If the client is not started, input iterator is empty,
1472+
input iterator yields non-RecordBatch objects, communication
1473+
with the worker fails, or the worker returns an unexpected
1474+
status or exception.
14741475
14751476
Example:
14761477
>>> with Client("vgi-example-worker") as client:
@@ -1522,6 +1523,13 @@ def table_in_out_function(
15221523
)
15231524
return
15241525

1526+
# Input iterator was empty - table-in-out functions require input
1527+
raise ClientError(
1528+
f"table_in_out_function requires at least one input batch. "
1529+
f"The input iterator for function '{function_name}' was empty. "
1530+
f"Use table_function() for functions that generate data without input."
1531+
)
1532+
15251533
def _table_in_out_function_parallel(
15261534
self,
15271535
*,
@@ -1912,7 +1920,7 @@ def scalar_function(
19121920
worker's registry.
19131921
input: Iterator yielding input RecordBatches. Must yield at least one
19141922
batch. The first batch's schema is used to initialize the IPC
1915-
stream. If the iterator is empty, no output is produced.
1923+
stream. Raises ClientError if the iterator is empty.
19161924
arguments: Optional Arguments container with positional and named
19171925
arguments to pass to the function. Defaults to empty Arguments().
19181926
bind_result_callback: Optional callback invoked with the raw bind
@@ -1932,9 +1940,10 @@ def scalar_function(
19321940
In parallel mode (max_processes > 1), output order is non-deterministic.
19331941
19341942
Raises:
1935-
ClientError: If the client is not started, input iterator yields
1936-
non-RecordBatch objects, communication with the worker fails,
1937-
or the worker returns an unexpected status or exception.
1943+
ClientError: If the client is not started, input iterator is empty,
1944+
input iterator yields non-RecordBatch objects, communication
1945+
with the worker fails, or the worker returns an unexpected
1946+
status or exception.
19381947
19391948
Example:
19401949
>>> with Client("vgi-example-worker") as client:
@@ -1987,6 +1996,13 @@ def scalar_function(
19871996
)
19881997
return
19891998

1999+
# Input iterator was empty - scalar functions require input
2000+
raise ClientError(
2001+
f"scalar_function requires at least one input batch. "
2002+
f"The input iterator for function '{function_name}' was empty. "
2003+
f"Use table_function() for functions that generate data without input."
2004+
)
2005+
19902006
def _scalar_function_parallel(
19912007
self,
19922008
*,

0 commit comments

Comments
 (0)