RamonZhou opened a new pull request, #58510:
URL: https://github.com/apache/spark/pull/58510

   ### What changes were proposed in this pull request?
   
   Follow-up to SPARK-58752, which let a session set environment variables for 
its Python UDF
   workers via `spark.pythonWorkerEnv.*`, but installed them for the regular 
scalar Python UDF
   only. This extends the environment to every Python worker a session launches 
for a Python
   function that session supplied.
   
   `PythonWorkerEnvironment.appliesTo(evalType)` is removed. While only three 
evaluation types
   were covered, an allowlist was a useful guard; now that every runner 
installs the environment
   it would be a second source of truth, and a new evaluation type routed 
through an existing
   runner would silently receive nothing. The scope is now the set of runners 
that install it, so
   such a type is covered by construction.
   
   Runners that gain the merge:
   
   | Runner | Serves |
   | --- | --- |
   | `ArrowPythonUDTFRunner` | Arrow UDTFs |
   | `CoGroupedArrowPythonRunner` | cogrouped map, pandas and Arrow |
   | `TransformWithStateInPySparkPythonRunner` | the `transformWithState` 
evaluation types |
   | `ApplyInPandasWithStatePythonRunner` | `applyInPandasWithState` |
   | `PythonPlannerRunner` | UDTF `analyze`, data-source planning, 
streaming-sink commit |
   | `PythonStreamingSourceRunner` | Python streaming data sources |
   | `StreamingPythonRunner` | `foreachBatch` |
   
   `BasePythonUDFRunner` and `BaseArrowPythonRunner` already merged, gated on 
`appliesTo`; with the
   gate gone they cover the pandas, Arrow, iterator, grouped-map, 
grouped-aggregate, window,
   `mapInPandas` and `mapInArrow` families that already reached them.
   
   `StreamingPythonRunner` lives in `core`, which cannot see `sql/core`, and 
`foreachBatch` uses it
   directly, so it takes an optional `sessionEnvVars` parameter that callers 
with a session supply.
   Rewriting the caller's `PythonFunction` instead was rejected: it would 
silently no-op for any
   implementation other than `SimplePythonFunction`. While there, `envVars` 
becomes a copy rather
   than an alias of `func.envVars`, which the runner mutates -- a function 
handed to two runners
   previously accumulated the other's variables.
   
   ### Why are the changes needed?
   
   A session's environment should not depend on which serialization mode or 
Python function family
   a user picked. Before this change a plain `udf` received the environment 
while the same logic
   written as a `pandas_udf`, a UDTF or `applyInPandas` did not, which is a 
difference users cannot
   predict from anything in the API.
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes. `spark.pythonWorkerEnv.*` now reaches the families listed above, where 
it previously had no
   effect. `docs/configuration.md` is updated accordingly.
   
   Two consequences worth calling out:
   
   - A worker that outlives the query that launched it keeps the values it 
started with. The
     streaming paths launch one worker per query rather than one per batch, so 
a change made while a
     streaming query is running reaches that query's worker only on restart. 
Documented.
   - Validation is part of installing the environment, so a malformed 
environment now fails the
     newly covered paths, which previously ignored it. Only reachable by 
writing straight to
     `SQLConf` and bypassing `RuntimeConfig.set`, which validates on write. 
Pinned by a test.
   
   A Spark Connect listener added through `addListener` runs its callbacks in 
the client process
   rather than in a worker Spark launched, so no session environment applies to 
it; the docs now say
   so. The server-side `PythonStreamingQueryListener` does launch a worker and 
does receive the
   environment, but it is reached only through the `add_listener` command, 
which PySpark no longer
   uses, so it has no PySpark-level test.
   
   ### How was this patch tested?
   
   `PythonWorkerEnvironmentSuite`, 46 tests. The two tests that asserted the 
old narrow scope are
   rewritten to assert the new contract, including one pinning that an invalid 
environment now fails
   runners that previously ignored it. New helpers cover the Arrow UDTF and 
cogrouped runners
   directly, since each declares its own `envVars` and would not be caught by 
testing a sibling.
   
   `test_python_worker_env`, 28 tests, run on both classic and Connect through 
the shared mixin:
   `mapInPandas`, `mapInArrow`, scalar and iterator pandas UDFs, 
`applyInPandas`, cogrouped
   `applyInPandas`, grouped-aggregate and window pandas UDFs, and row and Arrow 
UDTFs. The
   data-source test reads the variable in `schema()`, which runs in the 
planning worker, so it fails
   if only the execution-side workers received the environment.
   
   `test_parity_python_worker_env` adds a `foreachBatch` test that records what 
the worker observed
   in a file, using a file source with `availableNow` so the query is bounded 
rather than timed.
   
   Run locally: `PythonWorkerEnvironmentSuite` 46/46, classic 
`test_python_worker_env` 30/30,
   Connect parity 30/30, `dev/lint-scala` and both `dev/lint-python` ruff 
passes clean.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Opus 5
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to