Skip to content

Commit 5172f7f

Browse files
committed
Merge remote-tracking branch 'origin/main' into aling-poc-cte-optimization
2 parents 4b15904 + 495acce commit 5172f7f

7 files changed

Lines changed: 70 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
### Snowpark Python API Updates
66

7+
#### Bug Fixes
8+
9+
- Fixed a bug where `cloudpickle` could not be resolved when registering a Python stored procedure or UDF with `runtime_version='3.13'`.
10+
711
#### New Features
812

913
- Added `get_wif_token` to `snowflake.snowpark.secrets` for workload identity federation tokens on the Snowflake server (not available in SPCS file-based secret environments).

src/snowflake/snowpark/_internal/udf_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1276,7 +1276,7 @@ def resolve_imports_and_packages(
12761276
any(pkg.startswith("cloudpickle") for pkg in packages)
12771277
)
12781278
resolved_packages = packages + (
1279-
[f"cloudpickle=={cloudpickle.__version__}"]
1279+
[f"cloudpickle>={cloudpickle.__version__}"]
12801280
if not has_cloudpickle
12811281
else []
12821282
)

src/snowflake/snowpark/session.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2134,7 +2134,10 @@ def _get_req_identifiers_list(
21342134
if isinstance(m, str) and m not in result_dict:
21352135
res.append(m)
21362136
elif isinstance(m, ModuleType) and m.__name__ not in result_dict:
2137-
res.append(f"{m.__name__}=={m.__version__}")
2137+
if m.__name__ == "cloudpickle":
2138+
res.append(f"{m.__name__}>={m.__version__}")
2139+
else:
2140+
res.append(f"{m.__name__}=={m.__version__}")
21382141

21392142
return res
21402143

tests/integ/test_stored_procedure.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2736,3 +2736,27 @@ def normalize(rows):
27362736
]
27372737

27382738
assert normalize(df.collect()) == normalize(oracledb_real_data)
2739+
2740+
2741+
@pytest.mark.skipif(IS_IN_STORED_PROC, reason="not supported in stored proc")
2742+
def test_sproc_runtime_313_cloudpickle_ge_spec_compiles_and_executes(session):
2743+
"""Regression test for SNOW-3081273.
2744+
2745+
Verifies that a sproc targeting Python 3.13 deploys and executes correctly
2746+
even when the local cloudpickle version differs from the server-resolved one.
2747+
The auto-injected cloudpickle>=X spec must satisfy the 3.13 channel.
2748+
"""
2749+
multiplier = 7
2750+
2751+
def multiply(session_: Session, x: int) -> int:
2752+
return x * multiplier
2753+
2754+
sp = session.sproc.register(
2755+
multiply,
2756+
return_type=IntegerType(),
2757+
input_types=[IntegerType()],
2758+
packages=["snowflake-snowpark-python"],
2759+
runtime_version="3.13",
2760+
is_permanent=False,
2761+
)
2762+
assert sp(6) == 42

tests/unit/test_session.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import json
55
import logging
66
import os
7+
import types
78
from typing import Optional
89
from unittest import mock
910
from unittest.mock import MagicMock
@@ -880,6 +881,19 @@ def run_query_side_effect(query, **kwargs):
880881
ctx._aggregation_function_set = original_agg_set
881882

882883

884+
def test_get_req_identifiers_list_cloudpickle_only_uses_ge(mock_server_connection):
885+
"""Only cloudpickle is injected with >= to allow runtime-compatible resolution."""
886+
import cloudpickle as cp
887+
888+
session = Session(mock_server_connection)
889+
dummy_module = types.ModuleType("dummy_module")
890+
dummy_module.__version__ = "1.2.3"
891+
892+
result = session._get_req_identifiers_list([cp, dummy_module], {})
893+
assert result == [f"cloudpickle>={cp.__version__}", "dummy_module==1.2.3"]
894+
assert f"cloudpickle=={cp.__version__}" not in result
895+
896+
883897
def test_retrieve_aggregation_function_list_uses_single_internal_sync_query():
884898
"""Sync fallback executes exactly one internal metadata query."""
885899
import snowflake.snowpark.context as ctx

tests/unit/test_udf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def mock_callback(extension_function_properties):
9696
assert extension_function_properties.all_imports == ""
9797
# for >= 3.14, we use pypi by default, which auto adds cloudpickle
9898
assert extension_function_properties.all_packages == (
99-
f"'cloudpickle=={cloudpickle.__version__}'"
99+
f"'cloudpickle>={cloudpickle.__version__}'"
100100
if sys.version_info >= (3, 14)
101101
else ""
102102
)
@@ -134,7 +134,7 @@ def test_artifact_repository_adds_cloudpickle():
134134
assert all_packages is not None
135135
package_list = all_packages.split(",") if all_packages else []
136136
assert any(
137-
pkg.strip().strip("'").startswith("cloudpickle==") for pkg in package_list
137+
pkg.strip().strip("'").startswith("cloudpickle>=") for pkg in package_list
138138
), f"cloudpickle not found in packages: {all_packages}"
139139

140140
# Test case 2: packages already contains cloudpickle

tests/unit/test_udf_utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,3 +437,24 @@ def finish(self):
437437
# wrong class type should fallback to default arg names
438438
arg_names = get_func_arg_names(SumUDAF, TempObjectType.TABLE_FUNCTION, 2, True)
439439
assert arg_names == ["arg1", "arg2"]
440+
441+
442+
def test_resolve_imports_and_packages_non_conda_injects_cloudpickle_ge():
443+
"""Auto-injected cloudpickle uses >= not == so the server can resolve a
444+
compatible version for the target runtime (SNOW-3081273)."""
445+
import cloudpickle
446+
447+
_, _, _, all_packages, _, _ = resolve_imports_and_packages(
448+
session=None,
449+
object_type=TempObjectType.PROCEDURE,
450+
func=lambda: None,
451+
arg_names=[],
452+
udf_name="test_sp",
453+
stage_location=None,
454+
imports=None,
455+
packages=["snowflake-snowpark-python"],
456+
artifact_repository="SNOWPARK_PYTHON_TEST_REPOSITORY",
457+
)
458+
assert all_packages is not None
459+
assert f"cloudpickle>={cloudpickle.__version__}" in all_packages
460+
assert f"cloudpickle=={cloudpickle.__version__}" not in all_packages

0 commit comments

Comments
 (0)