[SPARK-58601][PYTHON] Tighten mapInPandas return-value contract to require a strict Iterator - #57800
[SPARK-58601][PYTHON] Tighten mapInPandas return-value contract to require a strict Iterator#57800Yicong-Huang wants to merge 1 commit into
Conversation
uros-b
left a comment
There was a problem hiding this comment.
Should we update the migration guide? To explain mapInPandas UDF returning a plain list of DataFrames (e.g. lambda it: [pdf for pdf in it]) previously succeeded and now raises UDF_RETURN_TYPE with "but is list"
Otherwise looks good, thank you @Yicong-Huang!
Thanks @uros-b! I am on the fence here. The public doc declares |
Actually I think we should just keep the change for now, and maybe issue a warning or sth
cloud-fan
left a comment
There was a problem hiding this comment.
0 blocking, 1 non-blocking, 0 nits.
The implementation and focused regression test are sound, but the observable compatibility change should be called out in the PySpark migration guide.
Suggestions (1)
- General: Add a PySpark 4.3 migration note explaining that mapInPandas now rejects lists and other non-Iterator iterables, and recommend returning iter(...).
Verification
Verified that the public mapInPandas documentation requires an iterator return, the worker now performs a strict collections.abc.Iterator check, and the closest mapInArrow analogue enforces the same outer-container contract through verify_return_type. The new test covers the formerly accepted list case and preserves existing coverage for invalid iterator elements.
PR metadata suggestions
- Correct the user-facing-change section: returning a list or another non-Iterator iterable now changes from success to UDF_RETURN_TYPE.
What changes were proposed in this pull request?
Tighten the
mapInPandas(SQL_MAP_PANDAS_ITER_UDF) return-value contract inworker.pyto require a strictIterator, matchingmapInArrow(SQL_MAP_ARROW_ITER_UDF).Previously the runtime check was
isinstance(result, Iterator) or hasattr(result, "__iter__"), which accepted any iterable (e.g. a returnedlist). It is nowisinstance(result, Iterator); a non-Iteratoriterable is rejected withUDF_RETURN_TYPE("iterator of pandas.DataFrame"/"pandas.Series"). The comment explaining whyverify_return_typewas not reused is removed accordingly.Why are the changes needed?
The declared signature has always been
PandasMapIterFunction = Callable[[Iterator[DataFrameLike]], Iterator[DataFrameLike]], and theDataFrame.mapInPandasdocstring states the function "outputs an iterator of pandas.DataFrames". The runtime, however, leniently accepted any iterable, diverging from the documented contract. This aligns the runtime with the declaredIterator[...]signature, mirroring the same tightening already done formapInArrowin SPARK-56612. It also unblocks reusingverify_return_typeat themapInPandassite withIterator[...](SPARK-58598).Does this PR introduce any user-facing change?
No. Minor tightening: a UDF returning a non-
Iteratoriterable (e.g.list) is now rejected withUDF_RETURN_TYPE, aligning the runtime with the documentedIterator[...]signature. This mirrors themapInArrowtightening in SPARK-56612.How was this patch tested?
Updated
test_pandas_map.py: removed the "returning list of DataFrames" positive case fromtest_map_in_pandas, and added alist_not_iternegative case tocheck_other_than_dataframe_iterasserting a returnedlistis rejected, mirroringtest_arrow_map.py::test_other_than_recordbatch_iter. Ranpyspark.sql.tests.pandas.test_pandas_mapand the Connect parity suite.Was this patch authored or co-authored using generative AI tooling?
No