Skip to content
Draft
9 changes: 6 additions & 3 deletions .github/workflows/daily_precommit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,23 @@ jobs:
- { os: {image_name: ubuntu-latest-64-cores, download_name: linux}, python-version: "3.11", cloud-provider: gcp }
- { os: {image_name: ubuntu-latest-64-cores, download_name: linux}, python-version: "3.12", cloud-provider: aws }
- { os: {image_name: ubuntu-latest-64-cores, download_name: linux}, python-version: "3.13", cloud-provider: azure }
- { os: {image_name: ubuntu-latest-64-cores, download_name: linux}, python-version: "3.14", cloud-provider: gcp }

# macOS + rotating cloud providers
- { os: {image_name: macos-latest, download_name: macos}, python-version: "3.9", cloud-provider: gcp }
- { os: {image_name: macos-latest, download_name: macos}, python-version: "3.10", cloud-provider: aws }
- { os: {image_name: macos-latest, download_name: macos}, python-version: "3.11", cloud-provider: azure }
- { os: {image_name: macos-latest, download_name: macos}, python-version: "3.12", cloud-provider: gcp }
- { os: {image_name: macos-latest, download_name: macos}, python-version: "3.13", cloud-provider: aws }
- { os: {image_name: macos-latest, download_name: macos}, python-version: "3.14", cloud-provider: azure }

# Windows + rotating cloud providers
- { os: {image_name: windows-latest-64-cores, download_name: windows}, python-version: "3.9", cloud-provider: azure }
- { os: {image_name: windows-latest-64-cores, download_name: windows}, python-version: "3.10", cloud-provider: gcp }
- { os: {image_name: windows-latest-64-cores, download_name: windows}, python-version: "3.11", cloud-provider: aws }
- { os: {image_name: windows-latest-64-cores, download_name: windows}, python-version: "3.12", cloud-provider: azure }
- { os: {image_name: windows-latest-64-cores, download_name: windows}, python-version: "3.13", cloud-provider: gcp }
- { os: {image_name: windows-latest-64-cores, download_name: windows}, python-version: "3.14", cloud-provider: aws }
steps:
- name: Checkout Code
uses: actions/checkout@v4
Expand Down Expand Up @@ -418,7 +421,7 @@ jobs:
image_name: windows-latest
- download_name: ubuntu
image_name: ubuntu-latest
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
cloud-provider: [azure]
steps:
- name: Checkout Code
Expand Down Expand Up @@ -488,7 +491,7 @@ jobs:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
python-version: ["3.9", "3.10", "3.11", "3.12"] # SNOW-2230787 test failing on Python 3.13
python-version: ["3.9", "3.10", "3.11", "3.12", "3.14"] # SNOW-2230787 test failing on Python 3.13
cloud-provider: [gcp]
protobuf-version: ["3.20.1", "4.25.3", "5.28.3"]
steps:
Expand Down Expand Up @@ -558,7 +561,7 @@ jobs:
os:
- image_name: macos-latest
download_name: macos # it includes doctest
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
cloud-provider: [azure]
steps:
- name: Checkout Code
Expand Down
16 changes: 14 additions & 2 deletions .github/workflows/precommit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,18 @@ jobs:
- python-version: "3.13"
cloud-provider: gcp
os: windows-latest-64-cores
# run py 3.14 tests on aws/ubuntu
- python-version: "3.14"
cloud-provider: aws
os: ubuntu-latest-64-cores
# run py 3.14 doctests on azure/macos
- python-version: "3.14"
cloud-provider: azure
os: macos-latest
# # run py 3.14 tests on gcp on windows
- python-version: "3.14"
cloud-provider: gcp
os: windows-latest-64-cores
Comment on lines +135 to +146
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should keep a subset of these? And/or remove an older version?

steps:
- name: Checkout Code
uses: actions/checkout@v4
Expand Down Expand Up @@ -233,7 +245,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version: ["3.13"]
python-version: ["3.14"]
cloud-provider: [azure]
steps:
- name: Checkout Code
Expand Down Expand Up @@ -436,7 +448,7 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
python-version: [ "3.13" ] # Test latest python
python-version: [ "3.14" ] # Test latest python
cloud-provider: [ gcp ] # Test only one csp
steps:
- name: Checkout Code
Expand Down
11 changes: 8 additions & 3 deletions recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ build:
string: "py311_{{ build_number }}" # [py==311]
string: "py312_{{ build_number }}" # [py==312]
string: "py313_{{ build_number }}" # [py==313]
string: "py314_{{ build_number }}" # [py==314]
{% endif %}

{% if noarch_build and py not in [39, 310, 311, 312, 313] %}
error: "Noarch build for Python version {{ py }} is not supported. Supported versions: 3.9, 3.10, 3.11, 3.12, or 3.13."
{% if noarch_build and py not in [39, 310, 311, 312, 313, 314] %}
error: "Noarch build for Python version {{ py }} is not supported. Supported versions: 3.9, 3.10, 3.11, 3.12, 3.13, or 3.14."
{% else %}
requirements:
host:
Expand All @@ -37,7 +38,9 @@ requirements:
- wheel
# Snowpark IR
- protobuf==3.20.1 # [py<=310]
- protobuf==4.25.3 # [py>310]
- protobuf==4.25.3 # [py>310 and py<314]
- protobuf==5.29.3 # [py>=314]
- libprotobuf >=5.29.3 # [py>=314]
# mypy-protobuf 3.7.0 requires protobuf >= 5.26
- mypy-protobuf <=3.6.0
run:
Expand All @@ -51,6 +54,8 @@ requirements:
- python >=3.12,<3.13.0a0
{% elif noarch_build and py == 313 %}
- python >=3.13,<3.14.0a0
{% elif noarch_build and py == 314 %}
- python >=3.14,<3.15.0a0
{% else %}
- python
{% endif %}
Expand Down
1 change: 1 addition & 0 deletions scripts/conda_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ conda build recipe/ -c sfe1ed40 --python=3.10 --numpy=1.21
conda build recipe/ -c sfe1ed40 --python=3.11 --numpy=1.23
conda build recipe/ -c sfe1ed40 --python=3.12 --numpy=1.26
conda build recipe/ -c sfe1ed40 --python=3.13 --numpy=2.2.0
conda build recipe/ -c sfe1ed40 --python=3.14 --numpy=2.4.2
8 changes: 5 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"python-dateutil", # Snowpark IR
"tzlocal", # Snowpark IR
]
REQUIRED_PYTHON_VERSION = ">=3.9, <3.14"
REQUIRED_PYTHON_VERSION = ">=3.9, <3.15"

if os.getenv("SNOWFLAKE_IS_PYTHON_RUNTIME_TEST", False):
REQUIRED_PYTHON_VERSION = ">=3.9"
Expand Down Expand Up @@ -71,7 +71,7 @@
# Snowpark pandas 3rd party library testing. Cap the scipy version because
# Snowflake cannot find newer versions of scipy for python 3.11+. See
# SNOW-2452791.
"scipy<=1.16.0",
"scipy<=1.16.3",
"statsmodels", # Snowpark pandas 3rd party library testing
"scikit-learn", # Snowpark pandas 3rd party library testing
# plotly version restricted due to foreseen change in query counts in version 6.0.0+
Expand All @@ -80,7 +80,8 @@
# snowflake-ml-python is available on python 3.12.
"snowflake-ml-python>=1.8.0; python_version<'3.12'",
"s3fs", # Used in tests that read CSV files from s3
"ray", # Used in data movement tests
# ray currently has no compatible wheels for Python 3.14.
"ray; python_version<'3.14'", # Used in data movement tests
]

# read the version
Expand Down Expand Up @@ -249,6 +250,7 @@ def run(self):
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Database",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries",
Expand Down
5 changes: 3 additions & 2 deletions src/snowflake/snowpark/_internal/analyzer/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved.
#

import copy
import uuid
from typing import TYPE_CHECKING, AbstractSet, Any, Dict, List, Optional, Tuple

Expand Down Expand Up @@ -158,7 +157,9 @@ def expr_id(self) -> uuid.UUID:
return self._expr_id

def __copy__(self):
new = copy.copy(super())
cls = self.__class__
new = cls.__new__(cls)
new.__dict__.update(self.__dict__)
new._expr_id = None # type: ignore
return new

Expand Down
10 changes: 6 additions & 4 deletions src/snowflake/snowpark/_internal/analyzer/snowflake_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from logging import getLogger
import re
import sys
import uuid
import uuid as uuid_lib
from collections import defaultdict, deque
from enum import Enum
from dataclasses import dataclass
Expand Down Expand Up @@ -383,7 +383,7 @@ def add_single_quote(string: str) -> str:
# and it's a reading XML query.

def search_read_file_node(
node: Union[SnowflakePlan, Selectable]
node: Union[SnowflakePlan, Selectable],
) -> Optional[ReadFileNode]:
source_plan = (
node.source_plan
Expand Down Expand Up @@ -435,7 +435,7 @@ def __init__(
# during the compilation stage.
schema_query: Optional[str],
post_actions: Optional[List["Query"]] = None,
expr_to_alias: Optional[Dict[uuid.UUID, str]] = None,
expr_to_alias: Optional[Dict[uuid_lib.UUID, str]] = None,
source_plan: Optional[LogicalPlan] = None,
is_ddl_on_temp_object: bool = False,
api_calls: Optional[List[Dict]] = None,
Expand Down Expand Up @@ -479,7 +479,9 @@ def __init__(
if self.session._join_alias_fix
else defaultdict(dict)
)
self._uuid = from_selectable_uuid if from_selectable_uuid else str(uuid.uuid4())
self._uuid = (
from_selectable_uuid if from_selectable_uuid else str(uuid_lib.uuid4())
)
# We set the query line intervals for the last query in the queries list
self.set_last_query_line_intervals()
# In the placeholder query, subquery (child) is held by the ID of query plan
Expand Down
2 changes: 1 addition & 1 deletion src/snowflake/snowpark/mock/_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ def handle_function_expression(
if param_name in exp.named_arguments:
type_hint = str(type_hints.get(param_name, ""))
keep_literal = "Column" not in type_hint
if type_hint == "typing.Optional[dict]":
if type_hint in ["typing.Optional[dict]", "dict | None"]:
to_pass_kwargs[param_name] = json.loads(
exp.named_arguments[param_name].sql.replace("'", '"')
)
Expand Down
7 changes: 6 additions & 1 deletion tests/integ/test_stored_procedure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1181,7 +1181,12 @@ def _(_: Session, x, y: int) -> int:
def _(_: Session, x: int, y: Union[int, float]) -> Union[int, float]:
return x + y

assert "invalid type typing.Union[int, float]" in str(ex_info)
msgs = [
"invalid type typing.Union[int, float]",
# python 3.14 changed the string representation of Union types
"invalid type int | float",
]
assert any(msg in str(ex_info) for msg in msgs)

with pytest.raises(TypeError) as ex_info:

Expand Down
7 changes: 6 additions & 1 deletion tests/integ/test_udf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1458,7 +1458,12 @@ def _(x, y: int) -> int:
def _(x: int, y: Union[int, float]) -> Union[int, float]:
return x + y

assert "invalid type typing.Union[int, float]" in str(ex_info)
msgs = [
"invalid type typing.Union[int, float]",
# python 3.14 changed the string representation of Union types
"invalid type int | float",
]
assert any(msg in str(ex_info) for msg in msgs)

with pytest.raises(ValueError) as ex_info:

Expand Down
6 changes: 5 additions & 1 deletion tests/mock/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,11 @@ def test_ai_complete(session):
# Mock the ai_complete function to return a simple response
@patch("ai_complete")
def mock_ai_complete(
model=None, prompt=None, response_format=None, model_parameters=None, **kwargs
model=None,
prompt=None,
response_format=None,
model_parameters=None,
**kwargs,
) -> ColumnEmulator:
"""Simple mock that returns 'AI response: <prompt>' for each input."""
assert (
Expand Down
6 changes: 4 additions & 2 deletions tests/unit/test_code_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#

import math
import pickle

import pytest

Expand Down Expand Up @@ -617,17 +618,18 @@ def func():

def test_variable_serialization():
nonlocalvar = "abc"
expected_hex = pickle.dumps(nonlocalvar).hex()

def add(x, y):
return x + y + nonlocalvar

assert (
generate_source_code(add, code_as_comment=False)
== """\
== f"""\
from __future__ import annotations
import pickle

nonlocalvar = pickle.loads(bytes.fromhex('80049507000000000000008c03616263942e')) # nonlocalvar is of type <class 'str'> and serialized by snowpark-python
nonlocalvar = pickle.loads(bytes.fromhex('{expected_hex}')) # nonlocalvar is of type <class 'str'> and serialized by snowpark-python
def add(x, y):
return x + y + nonlocalvar
func = add\
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ commands = coverage combine
coverage report -m
coverage xml -o {env:COV_REPORT_DIR:{toxworkdir}}/coverage.xml
coverage html -d {env:COV_REPORT_DIR:{toxworkdir}}/htmlcov --show-contexts
depends = py39, py310, py311, py312, py313
depends = py39, py310, py311, py312, py313, py314

[testenv:docs]
basepython = python3.9
Expand Down
Loading