This is an automated email from the ASF dual-hosted git repository.

HyukjinKwon pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new e4c894ad615e [SPARK-57760][SS][PYTHON][FOLLOWUP] Avoid importing numpy 
at module level in stateful_processor_api_client
e4c894ad615e is described below

commit e4c894ad615e41bf9d0a3c26a48d0e9b99ced611
Author: Kousuke Saruta <[email protected]>
AuthorDate: Fri Jul 10 07:06:26 2026 +0900

    [SPARK-57760][SS][PYTHON][FOLLOWUP] Avoid importing numpy at module level 
in stateful_processor_api_client
    
    ### What changes were proposed in this pull request?
    This PR makes `numpy` a lazy import in `stateful_processor_api_client.py` 
again. The fix introduces a dedicated `_load_numpy()` function that resolves 
numpy availability on first use (called from `_serialize_to_bytes`), and caches 
the result in module-level `has_numpy` / `np` variables. The 
`_normalize_state_value` function itself remains free of any import or 
initialization logic, keeping the hot path lean.
    
    ### Why are the changes needed?
    SPARK-57760 (#56786) moved `import numpy` to the top level of 
`stateful_processor_api_client.py` for micro-optimization. However, this module 
is loaded transitively by `import pyspark` via:
    
    import pyspark
    -> pyspark.sql
    -> pyspark.sql.streaming
    -> pyspark.sql.streaming.stateful_processor
    -> pyspark.sql.streaming.stateful_processor_api_client
    -> import numpy
    
    This causes `test_import_spark_libraries` to fail because the test asserts 
that `import pyspark` does not pull in any third-party packages other than py4j.
    
    https://github.com/apache/spark/actions/runs/29027743228/job/86156618984
    
    ```
    pyspark.tests.test_import_spark with python3.12 failed:
    
    Running tests...
    
    ----------------------------------------------------------------------
    
      test_import_spark_libraries 
(pyspark.tests.test_import_spark.ImportSparkTest.test_import_spark_libraries)
    
    We want to ensure "import pyspark" is fast. It matters when we spawn ... 
FAIL (0.252s)
    
    ======================================================================
    
    FAIL [0.252s]: test_import_spark_libraries 
(pyspark.tests.test_import_spark.ImportSparkTest.test_import_spark_libraries)
    
    We want to ensure "import pyspark" is fast. It matters when we spawn
    
    ----------------------------------------------------------------------
    
    Traceback (most recent call last):
    
      File "/__w/spark/spark/python/pyspark/tests/test_import_spark.py", line 
61, in test_import_spark_libraries
    
        self.fail(
    
    AssertionError: Unexpected 3rd party package 'numpy' imported during 
'import pyspark'
    ```
    
    ### Does this PR introduce _any_ user-facing change?
    No.
    
    ### How was this patch tested?
    - 
`pyspark.tests.test_import_spark.ImportSparkTest.test_import_spark_libraries` 
now passes.
    - Verified that `_normalize_state_value` correctly normalizes numpy scalars 
and nested containers (list/tuple/dict/namedtuple/Row) after `_load_numpy()` is 
triggered.
    - Confirmed `import pyspark` no longer imports numpy via `-X importtime`.
    ### Was this patch authored or co-authored using generative AI tooling?
    Kiro CLI / Claude
    
    Closes #57163 from sarutak/fix-numpy-import-time.
    
    Authored-by: Kousuke Saruta <[email protected]>
    Signed-off-by: Hyukjin Kwon <[email protected]>
---
 .../sql/streaming/stateful_processor_api_client.py | 76 +++++++++++++---------
 1 file changed, 46 insertions(+), 30 deletions(-)

diff --git a/python/pyspark/sql/streaming/stateful_processor_api_client.py 
b/python/pyspark/sql/streaming/stateful_processor_api_client.py
index 166ac52fa6d8..a679c499dc08 100644
--- a/python/pyspark/sql/streaming/stateful_processor_api_client.py
+++ b/python/pyspark/sql/streaming/stateful_processor_api_client.py
@@ -34,36 +34,51 @@ import uuid
 
 __all__ = ["StatefulProcessorApiClient", "StatefulProcessorHandleState"]
 
-try:
-    import numpy as np
-
-    has_numpy = True
-    SCALAR_TYPES = (bool, int, float, str, bytes, datetime, type(None))
-
-    def _normalize_state_value(v: Any) -> Any:
-        if type(v) in SCALAR_TYPES:  # Fast path for common scalar values.
-            return v
-        # Convert NumPy scalar values to Python primitive values.
-        if isinstance(v, np.generic):
-            return v.tolist()
-        # Named tuples (collections.namedtuple or typing.NamedTuple) and Row 
both
-        # require positional arguments and cannot be instantiated with a 
generator expression.
-        if isinstance(v, Row) or (isinstance(v, tuple) and hasattr(v, 
"_fields")):
-            return type(v)(*map(_normalize_state_value, v))
-        # List / tuple: recursively normalize each element.
-        if isinstance(v, (list, tuple)):
-            return type(v)(map(_normalize_state_value, v))
-        # Dict: normalize both keys and values.
-        if isinstance(v, dict):
-            return {_normalize_state_value(k): _normalize_state_value(val) for 
k, val in v.items()}
-        # Address a couple of pandas dtypes too.
-        if hasattr(v, "to_pytimedelta"):
-            return v.to_pytimedelta()
-        if hasattr(v, "to_pydatetime"):
-            return v.to_pydatetime()
+# None means not yet checked; True/False after _load_numpy() is called.
+has_numpy: Optional[bool] = None
+np = None
+
+SCALAR_TYPES = (bool, int, float, str, bytes, datetime, type(None))
+
+
+def _load_numpy() -> None:
+    """Lazily resolve numpy availability without importing it at module load 
time.
+
+    Importing numpy at the top level would slow down ``import pyspark``
+    (see test_import_spark_libraries).
+    """
+    global has_numpy, np
+    try:
+        import numpy
+
+        np = numpy
+        has_numpy = True
+    except ImportError:
+        has_numpy = False
+
+
+def _normalize_state_value(v: Any) -> Any:
+    if type(v) in SCALAR_TYPES:  # Fast path for common scalar values.
         return v
-except ImportError:
-    has_numpy = False
+    # Convert NumPy scalar values to Python primitive values.
+    if isinstance(v, np.generic):
+        return v.tolist()
+    # Named tuples (collections.namedtuple or typing.NamedTuple) and Row both
+    # require positional arguments and cannot be instantiated with a generator 
expression.
+    if isinstance(v, Row) or (isinstance(v, tuple) and hasattr(v, "_fields")):
+        return type(v)(*map(_normalize_state_value, v))
+    # List / tuple: recursively normalize each element.
+    if isinstance(v, (list, tuple)):
+        return type(v)(map(_normalize_state_value, v))
+    # Dict: normalize both keys and values.
+    if isinstance(v, dict):
+        return {_normalize_state_value(k): _normalize_state_value(val) for k, 
val in v.items()}
+    # Address a couple of pandas dtypes too.
+    if hasattr(v, "to_pytimedelta"):
+        return v.to_pytimedelta()
+    if hasattr(v, "to_pydatetime"):
+        return v.to_pydatetime()
+    return v
 
 
 class StatefulProcessorHandleState(Enum):
@@ -520,11 +535,12 @@ class StatefulProcessorApiClient:
         return self.utf8_deserializer.loads(self.sockfile)
 
     def _serialize_to_bytes(self, schema: StructType, data: Tuple) -> bytes:
+        if has_numpy is None:
+            _load_numpy()
         if has_numpy:
             converted = tuple(map(_normalize_state_value, data))
         else:
             converted = data
-
         return self.pickleSer.dumps(schema.toInternal(converted))
 
     def _deserialize_from_bytes(self, value: bytes) -> Any:


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

Reply via email to