Skip to content

Commit dee045c

Browse files
committed
Validate slice result structure
1 parent 42f7311 commit dee045c

2 files changed

Lines changed: 52 additions & 7 deletions

File tree

src/arraybridge/slice_processing.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,33 @@ def process_slices(image, func, args, kwargs, gpu_id=None):
4545
# Process each slice and handle special outputs
4646
main_outputs = []
4747
special_outputs_list = []
48+
returns_tuple = None
49+
tuple_arity = None
4850

49-
for slice_2d in slices_2d:
51+
for slice_index, slice_2d in enumerate(slices_2d):
5052
slice_result = func(slice_2d, *args, **kwargs)
5153

5254
# Check if result is a tuple (indicating special outputs)
53-
if isinstance(slice_result, tuple):
55+
result_is_tuple = isinstance(slice_result, tuple)
56+
if returns_tuple is None:
57+
returns_tuple = result_is_tuple
58+
elif result_is_tuple != returns_tuple:
59+
raise TypeError(
60+
"Slice processing cannot mix tuple and non-tuple results; "
61+
f"slice {slice_index} returned {type(slice_result).__name__}."
62+
)
63+
64+
if result_is_tuple:
65+
if not slice_result:
66+
raise ValueError("Slice processing result tuples cannot be empty")
67+
if tuple_arity is None:
68+
tuple_arity = len(slice_result)
69+
elif len(slice_result) != tuple_arity:
70+
raise ValueError(
71+
"Slice processing requires every result tuple to have the "
72+
f"same arity; slice {slice_index} returned {len(slice_result)}, "
73+
f"expected {tuple_arity}."
74+
)
5475
main_outputs.append(slice_result[0]) # First element is main output
5576
special_outputs_list.append(slice_result[1:]) # Rest are special outputs
5677
else:

tests/test_slice_processing.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,11 @@ def func_with_args_kwargs(slice_2d, multiplier, offset=0):
8383
np.testing.assert_array_equal(result, expected)
8484

8585
def test_process_slices_empty_special_outputs(self):
86-
"""Test process_slices when some slices return no special outputs."""
86+
"""Reject a tuple/non-tuple result boundary between slices."""
8787

88-
np.array([[[1]], [[2]]])
88+
from arraybridge.slice_processing import process_slices
89+
90+
image_3d = np.array([[[1]], [[2]]])
8991

9092
# Mix of single output and tuple output
9193
def mixed_func(slice_2d):
@@ -94,9 +96,31 @@ def mixed_func(slice_2d):
9496
else: # Second slice
9597
return slice_2d * 3
9698

97-
# This should work but might be complex; for now, assume consistent return types
98-
# In practice, functions should be consistent
99-
pass # Skip this test as it requires more complex logic
99+
with pytest.raises(TypeError, match="mix tuple and non-tuple"):
100+
process_slices(image_3d, mixed_func, (), {})
101+
102+
def test_process_slices_rejects_result_tuple_arity_drift(self):
103+
"""Reject side-output cardinality changes between slices."""
104+
105+
from arraybridge.slice_processing import process_slices
106+
107+
image_3d = np.array([[[1]], [[2]]])
108+
109+
def drifting_func(slice_2d):
110+
if np.sum(slice_2d) == 1:
111+
return slice_2d, "first"
112+
return slice_2d, "second", "surplus"
113+
114+
with pytest.raises(ValueError, match="same arity"):
115+
process_slices(image_3d, drifting_func, (), {})
116+
117+
def test_process_slices_rejects_empty_result_tuple(self):
118+
"""Reject a tuple that has no declared main output."""
119+
120+
from arraybridge.slice_processing import process_slices
121+
122+
with pytest.raises(ValueError, match="cannot be empty"):
123+
process_slices(np.ones((1, 1, 1)), lambda _slice: (), (), {})
100124

101125
@pytest.mark.parametrize(
102126
"shape",

0 commit comments

Comments
 (0)