Skip to content

Commit 975b299

Browse files
Yicong-HuangHyukjinKwon
authored andcommitted
[SPARK-56342][PYTHON] Tighten type hints for refactored eval type functions in worker.py
### What changes were proposed in this pull request? Tighten the type hints for `def func` signatures in `read_udfs()` for eval types that have been refactored to be self-contained. Specifically: - Replace `Iterator[Any]` with `Iterator["GroupedBatch"]` for grouped eval types (`SQL_GROUPED_AGG_ARROW_UDF`, `SQL_GROUPED_AGG_ARROW_ITER_UDF`, `SQL_WINDOW_AGG_ARROW_UDF`) - Rename the `batches` parameter to `data` across all refactored eval types for consistency, since the same call site passes either a flat stream or grouped stream - Use `for batch in data` for flat streams and `for group in data` for grouped streams - Define `GroupedBatch` and `CoGroupedBatch` type aliases in `pyspark.sql.pandas._typing` ### Why are the changes needed? After refactoring eval types to be self-contained in `read_udfs()`, the `def func` signatures used `Iterator[Any]` for grouped eval types, losing type information. Tightening these hints improves readability and enables better static analysis. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? Existing tests. ### Was this patch authored or co-authored using generative AI tooling? No. Closes #55178 from Yicong-Huang/SPARK-56342/tighten-type-hints. Authored-by: Yicong-Huang <17627829+Yicong-Huang@users.noreply.github.com> Signed-off-by: Hyukjin Kwon <gurwls223@apache.org>
1 parent 5bb6271 commit 975b299

2 files changed

Lines changed: 41 additions & 26 deletions

File tree

python/pyspark/sql/pandas/_typing/__init__.pyi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ ArrowGroupedAggUDFType = Literal[252]
6969
ArrowWindowAggUDFType = Literal[253]
7070
ArrowGroupedAggIterUDFType = Literal[254]
7171

72+
# Arrow stream types
73+
# A single group of Arrow batches (e.g., one key group in groupBy).
74+
GroupedBatch = Iterator[pyarrow.RecordBatch]
75+
# A group of two relations for cogroup operations (e.g., cogroupBy).
76+
CoGroupedBatch = Tuple[Iterator[pyarrow.RecordBatch], Iterator[pyarrow.RecordBatch]]
77+
7278
class ArrowVariadicScalarToScalarFunction(Protocol):
7379
def __call__(self, *_: pyarrow.Array) -> pyarrow.Array: ...
7480

python/pyspark/worker.py

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626
import inspect
2727
import itertools
2828
import json
29-
from typing import Any, Callable, Iterable, Iterator, Optional, Tuple, Union
29+
from typing import Any, Callable, Iterable, Iterator, Optional, Tuple, TYPE_CHECKING, Union
30+
31+
if TYPE_CHECKING:
32+
from pyspark.sql.pandas._typing import GroupedBatch
3033

3134
from pyspark.accumulators import (
3235
SpecialAccumulatorIds,
@@ -2573,12 +2576,12 @@ def read_udfs(pickleSer, infile, eval_type, runner_conf, eval_conf):
25732576
assert num_udfs == 1, "One MAP_ARROW_ITER UDF expected here."
25742577
udf_func: Callable[[Iterator[pa.RecordBatch]], Iterator[pa.RecordBatch]] = udfs[0][0]
25752578

2576-
def func(split_index: int, batches: Iterator[pa.RecordBatch]) -> Iterator[pa.RecordBatch]:
2579+
def func(split_index: int, data: Iterator[pa.RecordBatch]) -> Iterator[pa.RecordBatch]:
25772580
"""Apply mapInArrow UDF"""
25782581

25792582
# Pre-processing
25802583
input_batches: Iterator[pa.RecordBatch] = map(
2581-
ArrowBatchTransformer.flatten_struct, batches
2584+
ArrowBatchTransformer.flatten_struct, data
25822585
)
25832586

25842587
# invoke the UDF
@@ -2601,15 +2604,15 @@ def func(split_index: int, batches: Iterator[pa.RecordBatch]) -> Iterator[pa.Rec
26012604
prefers_large_types=runner_conf.use_large_var_types,
26022605
)
26032606

2604-
def func(split_index: int, batches: Iterator[pa.RecordBatch]) -> Iterator[pa.RecordBatch]:
2607+
def func(split_index: int, data: Iterator[pa.RecordBatch]) -> Iterator[pa.RecordBatch]:
26052608
"""Apply scalar Arrow UDFs"""
26062609

2607-
for input_batch in batches:
2610+
for batch in data:
26082611
output_batch = pa.RecordBatch.from_arrays(
26092612
[
26102613
udf_func(
2611-
*[input_batch.column(o) for o in args_offsets],
2612-
**{k: input_batch.column(v) for k, v in kwargs_offsets.items()},
2614+
*[batch.column(o) for o in args_offsets],
2615+
**{k: batch.column(v) for k, v in kwargs_offsets.items()},
26132616
)
26142617
for udf_func, args_offsets, kwargs_offsets, _ in udfs
26152618
],
@@ -2618,7 +2621,7 @@ def func(split_index: int, batches: Iterator[pa.RecordBatch]) -> Iterator[pa.Rec
26182621
output_batch = ArrowBatchTransformer.enforce_schema(
26192622
output_batch, combined_arrow_schema
26202623
)
2621-
verify_scalar_result(output_batch, input_batch.num_rows)
2624+
verify_scalar_result(output_batch, batch.num_rows)
26222625
yield output_batch
26232626

26242627
# profiling is not supported for UDF
@@ -2635,7 +2638,7 @@ def func(split_index: int, batches: Iterator[pa.RecordBatch]) -> Iterator[pa.Rec
26352638
return_type, timezone="UTC", prefers_large_types=runner_conf.use_large_var_types
26362639
)
26372640

2638-
def func(split_index: int, batches: Iterator[pa.RecordBatch]) -> Iterator[pa.RecordBatch]:
2641+
def func(split_index: int, data: Iterator[pa.RecordBatch]) -> Iterator[pa.RecordBatch]:
26392642
"""Apply scalar Arrow iterator UDF"""
26402643

26412644
num_input_rows = 0
@@ -2647,7 +2650,7 @@ def extract_args(batch: pa.RecordBatch):
26472650
return args[0] if len(args) == 1 else args
26482651

26492652
# Extract args from input batches (streaming)
2650-
args_iter = map(extract_args, batches)
2653+
args_iter = map(extract_args, data)
26512654

26522655
# Call UDF and verify result type (iterator of pa.Array)
26532656
verified_iter = verify_result(pa.Array)(udf_func(args_iter))
@@ -2697,9 +2700,11 @@ def process_results():
26972700
prefers_large_types=runner_conf.use_large_var_types,
26982701
)
26992702

2700-
def func(split_index: int, batches: Iterator[Any]) -> Iterator[pa.RecordBatch]:
2701-
for group_batches in batches:
2702-
batch_list = list(group_batches)
2703+
def grouped_func(
2704+
split_index: int, data: Iterator["GroupedBatch"]
2705+
) -> Iterator[pa.RecordBatch]:
2706+
for group in data:
2707+
batch_list = list(group)
27032708
if not batch_list:
27042709
continue
27052710
if hasattr(pa, "concat_batches"):
@@ -2722,7 +2727,7 @@ def func(split_index: int, batches: Iterator[Any]) -> Iterator[pa.RecordBatch]:
27222727
yield ArrowBatchTransformer.enforce_schema(batch, return_schema)
27232728

27242729
# profiling is not supported for UDF
2725-
return func, None, ser, ser
2730+
return grouped_func, None, ser, ser
27262731

27272732
if eval_type == PythonEvalType.SQL_GROUPED_AGG_ARROW_ITER_UDF:
27282733
import pyarrow as pa
@@ -2740,9 +2745,11 @@ def extract_args(batch):
27402745
args = tuple(batch.column(o) for o in args_offsets)
27412746
return args[0] if len(args) == 1 else args
27422747

2743-
def func(split_index: int, batches: Iterator[Any]) -> Iterator[pa.RecordBatch]:
2744-
for group_batches in batches:
2745-
batch_iter = map(extract_args, group_batches)
2748+
def grouped_func(
2749+
split_index: int, data: Iterator["GroupedBatch"]
2750+
) -> Iterator[pa.RecordBatch]:
2751+
for group in data:
2752+
batch_iter = map(extract_args, group)
27462753
result = udf_func(batch_iter)
27472754
# Drain remaining batches to maintain stream position
27482755
for _ in batch_iter:
@@ -2751,7 +2758,7 @@ def func(split_index: int, batches: Iterator[Any]) -> Iterator[pa.RecordBatch]:
27512758
yield ArrowBatchTransformer.enforce_schema(batch, return_schema)
27522759

27532760
# profiling is not supported for UDF
2754-
return func, None, ser, ser
2761+
return grouped_func, None, ser, ser
27552762

27562763
if eval_type == PythonEvalType.SQL_WINDOW_AGG_ARROW_UDF:
27572764
import pyarrow as pa
@@ -2766,9 +2773,11 @@ def func(split_index: int, batches: Iterator[Any]) -> Iterator[pa.RecordBatch]:
27662773
prefers_large_types=runner_conf.use_large_var_types,
27672774
)
27682775

2769-
def func(split_index: int, batches: Iterator[Any]) -> Iterator[pa.RecordBatch]:
2770-
for group_batches in batches:
2771-
batch_list = list(group_batches)
2776+
def grouped_func(
2777+
split_index: int, data: Iterator["GroupedBatch"]
2778+
) -> Iterator[pa.RecordBatch]:
2779+
for group in data:
2780+
batch_list = list(group)
27722781
if not batch_list:
27732782
continue
27742783
if hasattr(pa, "concat_batches"):
@@ -2817,7 +2826,7 @@ def func(split_index: int, batches: Iterator[Any]) -> Iterator[pa.RecordBatch]:
28172826
yield ArrowBatchTransformer.enforce_schema(batch, return_schema)
28182827

28192828
# profiling is not supported for UDF
2820-
return func, None, ser, ser
2829+
return grouped_func, None, ser, ser
28212830

28222831
if (
28232832
eval_type == PythonEvalType.SQL_ARROW_BATCHED_UDF
@@ -2868,8 +2877,8 @@ def _evaluate_batch_udf(udf_func, rows):
28682877
with ThreadPoolExecutor(max_workers=runner_conf.arrow_concurrency_level) as pool:
28692878
return list(pool.map(lambda row: udf_func(*row), rows))
28702879

2871-
def func(split_index: int, batches: Iterator[pa.RecordBatch]) -> Iterator[pa.RecordBatch]:
2872-
for input_batch in batches:
2880+
def func(split_index: int, data: Iterator[pa.RecordBatch]) -> Iterator[pa.RecordBatch]:
2881+
for input_batch in data:
28732882
num_rows = input_batch.num_rows
28742883

28752884
# --- Input: Arrow -> Python columns ---
@@ -2953,8 +2962,8 @@ def _evaluate_batch_udf_legacy(udf_func, rows):
29532962
with ThreadPoolExecutor(max_workers=runner_conf.arrow_concurrency_level) as pool:
29542963
return list(pool.map(lambda row: udf_func(*row), rows))
29552964

2956-
def func(split_index: int, batches: Iterator[pa.RecordBatch]) -> Iterator[pa.RecordBatch]:
2957-
for input_batch in batches:
2965+
def func(split_index: int, data: Iterator[pa.RecordBatch]) -> Iterator[pa.RecordBatch]:
2966+
for input_batch in data:
29582967
# --- Input: Arrow -> pandas columns ---
29592968
pandas_columns = ArrowBatchTransformer.to_pandas(
29602969
input_batch,

0 commit comments

Comments
 (0)