Skip to content

Commit 9d50aec

Browse files
committed
[SPARK-58601][PYTHON][FOLLOWUP] Default mapInBatch acceptAnyIterable to true
### What changes were proposed in this pull request? Follow-up to SPARK-58601, which tightened the `mapInPandas`/`mapInArrow` return-value contract to require a strict `Iterator` by default and added the internal escape-hatch config `spark.sql.execution.pythonUDF.mapInBatch.legacy.acceptAnyIterable.enabled` (default `false`). This PR flips that config's default to `true`, so the pre-4.3.0 lenient behavior (accepting any iterable such as a returned `list`) remains the default and can be disabled by setting the flag to `false` to opt into strict enforcement. The runtime code in `worker.py` and the config wiring are unchanged apart from the default. The 4.2-to-4.3 PySpark migration-guide note describing the breaking change is removed since there is no longer a default behavior change, and the negative tests that assert a returned `list` is rejected now set the flag to `false` explicitly. ### Why are the changes needed? Requiring a strict `Iterator` by default is a user-facing breaking change: a `mapInPandas`/`mapInArrow` UDF that returns a non-`Iterator` iterable (e.g. a `list`) previously worked and would start raising `UDF_RETURN_TYPE`. Defaulting the escape hatch to `true` preserves the established behavior for existing workloads while keeping the flag available for users who want the stricter contract, deferring any default change to a later decision. ### Does this PR introduce _any_ user-facing change? Yes, relative to the unreleased SPARK-58601 change on the unreleased branch. With this PR a `mapInPandas`/`mapInArrow` UDF that returns any iterable (e.g. a `list`) is accepted again by default, as it was before SPARK-58601. Setting `spark.sql.execution.pythonUDF.mapInBatch.legacy.acceptAnyIterable.enabled=false` restores the strict `Iterator`-only contract. Compared to released Spark versions there is no behavior change. ### How was this patch tested? Updated `test_pandas_map.py` and `test_arrow_map.py`: the negative cases asserting a returned `list` is rejected now run with the flag set to `false`; the existing `*_legacy_accept_any_iterable` tests continue to cover the lenient path. ### Was this patch authored or co-authored using generative AI tooling? No Closes #58436 from Yicong-Huang/SPARK-58601-default-true. Authored-by: Yicong Huang <17627829+Yicong-Huang@users.noreply.github.com> Signed-off-by: Yicong-Huang <17627829+Yicong-Huang@users.noreply.github.com>
1 parent 484866b commit 9d50aec

5 files changed

Lines changed: 19 additions & 13 deletions

File tree

python/docs/source/migration_guide/pyspark_upgrade.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@
1919
Upgrading PySpark
2020
==================
2121

22-
Upgrading from PySpark 4.2 to 4.3
23-
---------------------------------
24-
* In Spark 4.3, a ``mapInPandas`` UDF must return an iterator of ``pandas.DataFrame``\s; returning any other iterable such as a ``list`` now raises ``UDF_RETURN_TYPE``, matching the existing ``mapInArrow`` behavior and the declared ``Iterator[...]`` signature. To restore the previous behavior of accepting any iterable for both ``mapInPandas`` and ``mapInArrow``, set ``spark.sql.execution.pythonUDF.mapInBatch.legacy.acceptAnyIterable.enabled`` to ``true``.
25-
2622
Upgrading from PySpark 4.1 to 4.2
2723
---------------------------------
2824
* In Spark 4.2, the minimum supported version for PyArrow has been raised from 15.0.0 to 18.0.0 in PySpark.

python/pyspark/sql/tests/arrow/test_arrow_map.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,14 @@ def list_not_iter(_):
167167
):
168168
(self.spark.range(10, numPartitions=3).mapInArrow(bad_iter_elem, "a int").count())
169169

170-
with self.assertRaisesRegex(
171-
PythonException,
172-
r"iterator of pyarrow\.RecordBatch.*\blist\b",
170+
with (
171+
self.sql_conf(
172+
{"spark.sql.execution.pythonUDF.mapInBatch.legacy.acceptAnyIterable.enabled": False}
173+
),
174+
self.assertRaisesRegex(
175+
PythonException,
176+
r"iterator of pyarrow\.RecordBatch.*\blist\b",
177+
),
173178
):
174179
(self.spark.range(10, numPartitions=3).mapInArrow(list_not_iter, "a int").count())
175180

python/pyspark/sql/tests/pandas/test_pandas_map.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,15 @@ def list_not_iter(iterator):
232232
):
233233
(self.spark.range(10, numPartitions=3).mapInPandas(bad_iter_elem, "a int").count())
234234

235-
with self.assertRaisesRegex(
236-
PythonException,
237-
"Return type of the user-defined function should be iterator of pandas.DataFrame, "
238-
"but is list",
235+
with (
236+
self.sql_conf(
237+
{"spark.sql.execution.pythonUDF.mapInBatch.legacy.acceptAnyIterable.enabled": False}
238+
),
239+
self.assertRaisesRegex(
240+
PythonException,
241+
"Return type of the user-defined function should be iterator of pandas.DataFrame, "
242+
"but is list",
243+
),
239244
):
240245
(self.spark.range(10, numPartitions=3).mapInPandas(list_not_iter, "a int").count())
241246

python/pyspark/worker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def map_in_batch_legacy_accept_any_iterable(self) -> bool:
152152
return (
153153
self.get(
154154
"spark.sql.execution.pythonUDF.mapInBatch.legacy.acceptAnyIterable.enabled",
155-
"false",
155+
"true",
156156
)
157157
== "true"
158158
)

sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5565,7 +5565,7 @@ object SQLConf {
55655565
.version("4.3.0")
55665566
.withBindingPolicy(ConfigBindingPolicy.SESSION)
55675567
.booleanConf
5568-
.createWithDefault(false)
5568+
.createWithDefault(true)
55695569

55705570
val PYTHON_PLANNER_EXEC_MEMORY =
55715571
buildConf("spark.sql.planner.pythonExecution.memory")

0 commit comments

Comments
 (0)