From bf67ddc9b6eb2f8dca2bfc2408093dd73441af38 Mon Sep 17 00:00:00 2001 From: Jonathan Shi Date: Thu, 7 May 2026 11:27:20 -0700 Subject: [PATCH 1/4] remove 3.9 support --- .github/workflows/daily_jupyter_nb_test.yml | 6 ++-- ...10.yml => daily_modin_precommit_py310.yml} | 20 +++++------ .../daily_modin_precommit_py311_py312.yml | 6 ++-- .github/workflows/daily_precommit.yml | 34 ++++++++----------- .github/workflows/precommit.yml | 23 +++++-------- ci/test_fips.sh | 2 +- .../API-Documentation-Generation.ipynb | 2 +- .../_internal/analyzer/analyzer_utils.py | 9 +---- .../_internal/analyzer/select_statement.py | 9 +---- .../_internal/analyzer/snowflake_plan.py | 8 +---- .../_internal/analyzer/snowflake_plan_node.py | 10 +----- .../_internal/analyzer/table_function.py | 10 +----- .../snowpark/_internal/code_generation.py | 8 +---- .../snowpark/_internal/debug_utils.py | 2 +- .../snowpark/_internal/type_utils.py | 8 +---- src/snowflake/snowpark/_internal/udf_utils.py | 8 +---- .../_internal/xml_schema_inference.py | 2 +- src/snowflake/snowpark/column.py | 9 +---- src/snowflake/snowpark/dataframe.py | 9 +---- .../snowpark/dataframe_na_functions.py | 10 +----- src/snowflake/snowpark/dataframe_reader.py | 10 +----- .../snowpark/dataframe_stat_functions.py | 10 +----- src/snowflake/snowpark/dataframe_writer.py | 10 +----- src/snowflake/snowpark/files.py | 9 +---- src/snowflake/snowpark/functions.py | 9 +---- .../snowpark/modin/plugin/__init__.py | 4 +-- .../plugin/_internal/ordered_dataframe.py | 9 +---- .../plugin/extensions/indexing_overrides.py | 2 +- .../modin/plugin/extensions/pd_extensions.py | 2 +- .../snowpark/modin/plugin/extensions/utils.py | 4 +-- src/snowflake/snowpark/row.py | 10 +----- src/snowflake/snowpark/session.py | 8 +---- src/snowflake/snowpark/stored_procedure.py | 8 +---- src/snowflake/snowpark/table.py | 9 +---- src/snowflake/snowpark/types.py | 8 +---- src/snowflake/snowpark/udaf.py | 8 +---- src/snowflake/snowpark/udf.py | 8 +---- src/snowflake/snowpark/udtf.py | 8 +---- src/snowflake/snowpark/window.py | 8 +---- 39 files changed, 76 insertions(+), 263 deletions(-) rename .github/workflows/{daily_modin_precommit_py39_py310.yml => daily_modin_precommit_py310.yml} (98%) diff --git a/.github/workflows/daily_jupyter_nb_test.yml b/.github/workflows/daily_jupyter_nb_test.yml index 435b6432a3..039aba9c53 100644 --- a/.github/workflows/daily_jupyter_nb_test.yml +++ b/.github/workflows/daily_jupyter_nb_test.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -46,7 +46,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -78,7 +78,7 @@ jobs: download_name: macos - image_name: windows-latest download_name: windows - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11"] # FINANCIAL__ECONOMIC_ESSENTIALS database unavailable in gcp, azure cloud-provider: [aws] steps: diff --git a/.github/workflows/daily_modin_precommit_py39_py310.yml b/.github/workflows/daily_modin_precommit_py310.yml similarity index 98% rename from .github/workflows/daily_modin_precommit_py39_py310.yml rename to .github/workflows/daily_modin_precommit_py310.yml index 57d18da4cf..b7cc08cc33 100644 --- a/.github/workflows/daily_modin_precommit_py39_py310.yml +++ b/.github/workflows/daily_modin_precommit_py310.yml @@ -1,6 +1,6 @@ # This is copied from original daily_precommit.yml with one change: only run Snowpark pandas tests -name: Daily Snowpark pandas API test with Py3.9 and Py3.10 +name: Daily Snowpark pandas API test with Py3.10 on: schedule: # 12 AM UTC @@ -23,7 +23,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -50,7 +50,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -80,7 +80,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -105,7 +105,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -136,7 +136,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -171,7 +171,7 @@ jobs: download_name: macos - image_name: windows-latest-64-cores download_name: windows - python-version: ["3.9", "3.10"] + python-version: ["3.10"] cloud-provider: [aws, azure, gcp] steps: - name: Checkout Code @@ -418,7 +418,7 @@ jobs: python-version: "3.10" cloud-provider: gcp - os: ubuntu-latest-64-cores - python-version: "3.9" + python-version: "3.10" cloud-provider: aws steps: - name: Checkout Code @@ -509,7 +509,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -564,7 +564,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: diff --git a/.github/workflows/daily_modin_precommit_py311_py312.yml b/.github/workflows/daily_modin_precommit_py311_py312.yml index fcda7f90df..b3bedf8f70 100644 --- a/.github/workflows/daily_modin_precommit_py311_py312.yml +++ b/.github/workflows/daily_modin_precommit_py311_py312.yml @@ -23,7 +23,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -50,7 +50,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -184,7 +184,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: diff --git a/.github/workflows/daily_precommit.yml b/.github/workflows/daily_precommit.yml index 3c87ba3de5..0328fd2f3c 100644 --- a/.github/workflows/daily_precommit.yml +++ b/.github/workflows/daily_precommit.yml @@ -24,7 +24,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -49,7 +49,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -76,7 +76,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -105,7 +105,6 @@ jobs: matrix: include: # Ubuntu + rotating cloud providers - - { os: {image_name: ubuntu-latest-64-cores, download_name: linux}, python-version: "3.9", cloud-provider: aws } - { os: {image_name: ubuntu-latest-64-cores, download_name: linux}, python-version: "3.10", cloud-provider: azure } - { 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 } @@ -113,7 +112,6 @@ jobs: - { 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 } @@ -121,7 +119,6 @@ jobs: - { 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 } @@ -210,9 +207,8 @@ jobs: sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 unixodbc-dev shell: bash - name: Run data source tests - # psycopg2 is not supported on macos 3.9 # TODO: enable datasource tests for 3.14 - if: ${{ !(contains('macos', matrix.os.download_name) && matrix.python-version == '3.9') && !(matrix.python-version == '3.14') }} + if: ${{ !(matrix.python-version == '3.14') }} run: python -m tox -e datasource env: PYTHON_VERSION: ${{ matrix.python-version }} @@ -234,7 +230,7 @@ jobs: .tox/coverage.xml test-fips: - name: Test FIPS py-linux-3.9-${{ matrix.cloud-provider }} + name: Test FIPS py-linux-3.10-${{ matrix.cloud-provider }} needs: build runs-on: ubuntu-latest-64-cores strategy: @@ -261,7 +257,7 @@ jobs: - name: Run tests run: ./ci/test_fips_docker.sh env: - PYTHON_VERSION: 3.9 + PYTHON_VERSION: '3.10' cloud_provider: ${{ matrix.cloud-provider }} PYTEST_ADDOPTS: --color=yes --tb=short TOX_PARALLEL_NO_SPINNER: 1 @@ -269,7 +265,7 @@ jobs: - uses: actions/upload-artifact@v4 with: include-hidden-files: true - name: coverage_linux-fips-3.9-${{ matrix.cloud-provider }} + name: coverage_linux-fips-3.10-${{ matrix.cloud-provider }} path: | .coverage coverage.xml @@ -284,7 +280,7 @@ jobs: os: - image_name: macos-latest download_name: macos # it includes doctest - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] cloud-provider: [aws] steps: - name: Checkout Code @@ -362,7 +358,7 @@ jobs: os: - image_name: macos-latest download_name: macos - python-version: ["3.9"] + python-version: ["3.10"] cloud-provider: [aws] steps: - name: Checkout Code @@ -437,7 +433,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", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] cloud-provider: [azure] steps: - name: Checkout Code @@ -507,7 +503,7 @@ jobs: fail-fast: false matrix: os: [macos-latest, windows-latest, ubuntu-latest] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.14"] # SNOW-2230787 test failing on Python 3.13 + python-version: ["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: @@ -577,7 +573,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", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] cloud-provider: [azure] steps: - name: Checkout Code @@ -655,7 +651,7 @@ jobs: matrix: include: - os: macos-latest - python-version: "3.9" + python-version: "3.10" cloud-provider: azure - os: ubuntu-latest python-version: "3.12" @@ -747,7 +743,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -802,7 +798,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: diff --git a/.github/workflows/precommit.yml b/.github/workflows/precommit.yml index 9b31ac9d4a..62d7147dfe 100644 --- a/.github/workflows/precommit.yml +++ b/.github/workflows/precommit.yml @@ -26,7 +26,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -51,7 +51,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -78,7 +78,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -108,10 +108,6 @@ jobs: # matrix is empty for pre-commit, tests are only added # through the include directive include: - # only run py3.9 tests on ubuntu+aws to isolate 3.9 failures - - python-version: "3.9" - os: ubuntu-latest-64-cores - cloud-provider: aws # only run azure tests with latest python and ubuntu - cloud-provider: azure python-version: "3.12" @@ -207,8 +203,7 @@ jobs: sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 unixodbc-dev shell: bash - name: Run data source tests - # psycopg2 is not supported on macos 3.9 - if: ${{ !(matrix.os == 'macos-latest' && matrix.python-version == '3.9') }} + if: ${{ !(matrix.python-version == '3.14') }} run: python -m tox -e datasource env: PYTHON_VERSION: ${{ matrix.python-version }} @@ -387,7 +382,7 @@ jobs: fail-fast: false matrix: os: [ macos-latest ] - python-version: [ "3.9"] + python-version: [ "3.10"] cloud-provider: [ aws ] steps: - name: Checkout Code @@ -515,7 +510,7 @@ jobs: # version/cloud/OS permutation coverage. include: # The steps below are configured to run only doctests for macos-3.12-aws - - python-version: "3.9" + - python-version: "3.10" os: macos-latest cloud-provider: aws - cloud-provider: gcp @@ -620,7 +615,7 @@ jobs: os: - image_name: macos-latest download_name: macos - python-version: ["3.9"] + python-version: ["3.10"] cloud-provider: [aws] steps: - name: Checkout Code @@ -696,7 +691,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: @@ -758,7 +753,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Set up uv uses: astral-sh/setup-uv@v6 with: diff --git a/ci/test_fips.sh b/ci/test_fips.sh index 5e5590cd3f..dda0c53222 100755 --- a/ci/test_fips.sh +++ b/ci/test_fips.sh @@ -8,7 +8,7 @@ THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" SNOWPARK_DIR="$( dirname "${THIS_DIR}")" SNOWPARK_WHL="$(ls $SNOWPARK_DIR/dist/*.whl | sort -r | head -n 1)" -python3.9 -m venv fips_env +python3.10 -m venv fips_env source fips_env/bin/activate export PATH=/usr/local/bin:$PATH export LD_LIBRARY_PATH=/usr/local/lib64/:/usr/local/lib/:$LD_LIBRARY_PATH diff --git a/docs/modin_api_coverage/API-Documentation-Generation.ipynb b/docs/modin_api_coverage/API-Documentation-Generation.ipynb index 8ae0c96ce6..93d66e84c5 100644 --- a/docs/modin_api_coverage/API-Documentation-Generation.ipynb +++ b/docs/modin_api_coverage/API-Documentation-Generation.ipynb @@ -774,7 +774,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.18" + "version": "3.10.0" } }, "nbformat": 4, diff --git a/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py b/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py index 5333394464..0429f26576 100644 --- a/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py +++ b/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py @@ -6,7 +6,6 @@ import json import math import os -import sys import tempfile from typing import Any, Dict, List, Optional, Tuple, Union, Literal, Sequence @@ -52,13 +51,7 @@ from snowflake.snowpark.row import Row from snowflake.snowpark.types import DataType -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable LEFT_PARENTHESIS = "(" RIGHT_PARENTHESIS = ")" diff --git a/src/snowflake/snowpark/_internal/analyzer/select_statement.py b/src/snowflake/snowpark/_internal/analyzer/select_statement.py index f4ea001917..9e9e502e88 100644 --- a/src/snowflake/snowpark/_internal/analyzer/select_statement.py +++ b/src/snowflake/snowpark/_internal/analyzer/select_statement.py @@ -2,7 +2,6 @@ # Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved. # -import sys import uuid import re from abc import ABC, abstractmethod @@ -90,13 +89,7 @@ ) import snowflake.snowpark.context as context -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable SET_UNION = analyzer_utils.UNION SET_UNION_ALL = analyzer_utils.UNION_ALL diff --git a/src/snowflake/snowpark/_internal/analyzer/snowflake_plan.py b/src/snowflake/snowpark/_internal/analyzer/snowflake_plan.py index d1f24a8325..d42549fcff 100644 --- a/src/snowflake/snowpark/_internal/analyzer/snowflake_plan.py +++ b/src/snowflake/snowpark/_internal/analyzer/snowflake_plan.py @@ -143,13 +143,7 @@ from snowflake.snowpark.types import StructType import snowflake.snowpark.context as context -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable _logger = getLogger(__name__) diff --git a/src/snowflake/snowpark/_internal/analyzer/snowflake_plan_node.py b/src/snowflake/snowpark/_internal/analyzer/snowflake_plan_node.py index 0d2169e9eb..0fc457ecf2 100644 --- a/src/snowflake/snowpark/_internal/analyzer/snowflake_plan_node.py +++ b/src/snowflake/snowpark/_internal/analyzer/snowflake_plan_node.py @@ -3,7 +3,7 @@ # Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved. # -import sys +from collections.abc import Iterable from enum import Enum from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple @@ -17,14 +17,6 @@ from snowflake.snowpark.row import Row from snowflake.snowpark.types import StructType -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable - if TYPE_CHECKING: from snowflake.snowpark import Session from snowflake.snowpark.udtf import UserDefinedTableFunction diff --git a/src/snowflake/snowpark/_internal/analyzer/table_function.py b/src/snowflake/snowpark/_internal/analyzer/table_function.py index bb19a88a05..029f2cdaba 100644 --- a/src/snowflake/snowpark/_internal/analyzer/table_function.py +++ b/src/snowflake/snowpark/_internal/analyzer/table_function.py @@ -2,7 +2,7 @@ # Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved. # -import sys +from collections.abc import Iterable from typing import Dict, List, Optional from snowflake.snowpark._internal.analyzer.expression import Expression @@ -13,14 +13,6 @@ from snowflake.snowpark._internal.analyzer.snowflake_plan_node import LogicalPlan from snowflake.snowpark._internal.analyzer.sort_expression import SortOrder -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable - class TableFunctionPartitionSpecDefinition(Expression): def __init__( diff --git a/src/snowflake/snowpark/_internal/code_generation.py b/src/snowflake/snowpark/_internal/code_generation.py index dbb63b15eb..9a6573bbd8 100644 --- a/src/snowflake/snowpark/_internal/code_generation.py +++ b/src/snowflake/snowpark/_internal/code_generation.py @@ -17,13 +17,7 @@ import opcode -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable logger = getLogger(__name__) diff --git a/src/snowflake/snowpark/_internal/debug_utils.py b/src/snowflake/snowpark/_internal/debug_utils.py index c881736bf7..89cee1c4a1 100644 --- a/src/snowflake/snowpark/_internal/debug_utils.py +++ b/src/snowflake/snowpark/_internal/debug_utils.py @@ -64,7 +64,7 @@ def _read_file( code_lines[0] = code_lines[0][start_column:] code_lines[-1] = code_lines[-1][:end_column] else: - # For python 3.9/3.10, we do not extract the end line from the source code + # For python 3.10, we do not extract the end line from the source code # so we just read the start line and return. for line in itertools.islice(f, start_line - 1, start_line): code_lines.append(line) diff --git a/src/snowflake/snowpark/_internal/type_utils.py b/src/snowflake/snowpark/_internal/type_utils.py index c016e0f0f0..bc663025b6 100644 --- a/src/snowflake/snowpark/_internal/type_utils.py +++ b/src/snowflake/snowpark/_internal/type_utils.py @@ -83,13 +83,7 @@ File, ) -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -try: - from typing import Iterable # noqa: F401 -except ImportError: - from collections.abc import Iterable # noqa: F401 +from collections.abc import Iterable # noqa: F401 if installed_pandas: from snowflake.snowpark.types import ( diff --git a/src/snowflake/snowpark/_internal/udf_utils.py b/src/snowflake/snowpark/_internal/udf_utils.py index 5e3327fc08..7ebfff5928 100644 --- a/src/snowflake/snowpark/_internal/udf_utils.py +++ b/src/snowflake/snowpark/_internal/udf_utils.py @@ -71,13 +71,7 @@ PandasSeriesType, ) -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable logger = getLogger(__name__) diff --git a/src/snowflake/snowpark/_internal/xml_schema_inference.py b/src/snowflake/snowpark/_internal/xml_schema_inference.py index 1a8ec7ee36..82b5a10b71 100644 --- a/src/snowflake/snowpark/_internal/xml_schema_inference.py +++ b/src/snowflake/snowpark/_internal/xml_schema_inference.py @@ -112,7 +112,7 @@ def _infer_primitive_type(text: str) -> DataType: pass # Timestamp (Spark default: TimestampFormatter with CAST logic) - # Backward compatibility: Python 3.9 fromisoformat doesn't support 'Z' suffix; replace with +00:00 + # Backward compatibility: Python 3.10 fromisoformat doesn't support 'Z' suffix; replace with +00:00 try: ts_text = text.replace("Z", "+00:00") if text.endswith("Z") else text datetime.fromisoformat(ts_text) diff --git a/src/snowflake/snowpark/column.py b/src/snowflake/snowpark/column.py index 308883a536..2cc9c2289c 100644 --- a/src/snowflake/snowpark/column.py +++ b/src/snowflake/snowpark/column.py @@ -3,7 +3,6 @@ # Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved. # -import sys import typing from typing import Any, Optional, Union @@ -100,13 +99,7 @@ ) from snowflake.snowpark.window import Window, WindowSpec -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable def _to_col_if_lit( diff --git a/src/snowflake/snowpark/dataframe.py b/src/snowflake/snowpark/dataframe.py index be9065df05..ef8868e1c0 100644 --- a/src/snowflake/snowpark/dataframe.py +++ b/src/snowflake/snowpark/dataframe.py @@ -8,7 +8,6 @@ import itertools import random import re -import sys from collections import Counter from decimal import Decimal from functools import cached_property, reduce @@ -235,13 +234,7 @@ YearMonthIntervalType, ) -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable if TYPE_CHECKING: import modin.pandas # pragma: no cover diff --git a/src/snowflake/snowpark/dataframe_na_functions.py b/src/snowflake/snowpark/dataframe_na_functions.py index 8c2ceb4230..1f73e2a30e 100644 --- a/src/snowflake/snowpark/dataframe_na_functions.py +++ b/src/snowflake/snowpark/dataframe_na_functions.py @@ -4,7 +4,7 @@ # import math -import sys +from collections.abc import Iterable from logging import getLogger from typing import Dict, Optional, Union @@ -33,14 +33,6 @@ LongType, ) -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable - _logger = getLogger(__name__) diff --git a/src/snowflake/snowpark/dataframe_reader.py b/src/snowflake/snowpark/dataframe_reader.py index f435e9a1b4..b5fb5de136 100644 --- a/src/snowflake/snowpark/dataframe_reader.py +++ b/src/snowflake/snowpark/dataframe_reader.py @@ -4,9 +4,9 @@ import json import os import re -import sys import time from collections import defaultdict +from collections.abc import Iterable from logging import getLogger from typing import Any, Dict, List, Literal, Optional, Tuple, Union, Callable from datetime import datetime @@ -103,14 +103,6 @@ StructField, ) -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable - logger = getLogger(__name__) _NOT_NULL_RE = re.compile(r"\s+NOT\s+NULL", re.IGNORECASE) diff --git a/src/snowflake/snowpark/dataframe_stat_functions.py b/src/snowflake/snowpark/dataframe_stat_functions.py index 9795ff3f9e..5d768c6078 100644 --- a/src/snowflake/snowpark/dataframe_stat_functions.py +++ b/src/snowflake/snowpark/dataframe_stat_functions.py @@ -2,7 +2,7 @@ # Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved. # -import sys +from collections.abc import Iterable from functools import reduce from typing import Callable, Dict, List, Optional, Union @@ -34,14 +34,6 @@ covar_samp, ) -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable - from logging import getLogger _logger = getLogger(__name__) diff --git a/src/snowflake/snowpark/dataframe_writer.py b/src/snowflake/snowpark/dataframe_writer.py index c1d2c4da41..bce50edf91 100644 --- a/src/snowflake/snowpark/dataframe_writer.py +++ b/src/snowflake/snowpark/dataframe_writer.py @@ -2,7 +2,7 @@ # Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved. # -import sys +from collections.abc import Iterable from logging import getLogger from typing import Any, Dict, List, Literal, Optional, Union, overload @@ -54,14 +54,6 @@ from snowflake.snowpark.mock._connection import MockServerConnection from snowflake.snowpark.row import Row -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable - WRITER_OPTIONS_ALIAS_MAP = { "SEP": "FIELD_DELIMITER", "LINESEP": "RECORD_DELIMITER", diff --git a/src/snowflake/snowpark/files.py b/src/snowflake/snowpark/files.py index f0c38c1db1..3be46b45a8 100644 --- a/src/snowflake/snowpark/files.py +++ b/src/snowflake/snowpark/files.py @@ -6,7 +6,6 @@ from __future__ import annotations import array -import sys import tempfile from io import ( RawIOBase, @@ -23,13 +22,7 @@ from snowflake.snowpark.context import get_active_session import logging -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable _WRITE_MODE_ERR_MSG = ( diff --git a/src/snowflake/snowpark/functions.py b/src/snowflake/snowpark/functions.py index ca0a6a2c0a..153d418817 100644 --- a/src/snowflake/snowpark/functions.py +++ b/src/snowflake/snowpark/functions.py @@ -158,7 +158,6 @@ """ import functools -import sys import typing from functools import reduce import json @@ -247,13 +246,7 @@ from snowflake.snowpark.udf import UDFRegistration, UserDefinedFunction from snowflake.snowpark.udtf import UDTFRegistration, UserDefinedTableFunction -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable @overload diff --git a/src/snowflake/snowpark/modin/plugin/__init__.py b/src/snowflake/snowpark/modin/plugin/__init__.py index a27094dd9e..be70dcddf1 100644 --- a/src/snowflake/snowpark/modin/plugin/__init__.py +++ b/src/snowflake/snowpark/modin/plugin/__init__.py @@ -9,9 +9,9 @@ from packaging import version -if sys.version_info.major == 3 and sys.version_info.minor == 8: +if sys.version_info.major == 3 and sys.version_info.minor <= 9: raise RuntimeError( - "Snowpark pandas does not support Python 3.8. Please update to Python 3.9 or later." + "Snowpark pandas does not support Python 3.9 or earlier. Please update to Python 3.10 or later." ) # pragma: no cover # pandas import needs to come before Python version + modin checks, diff --git a/src/snowflake/snowpark/modin/plugin/_internal/ordered_dataframe.py b/src/snowflake/snowpark/modin/plugin/_internal/ordered_dataframe.py index febcc61ea7..bc2b6097c8 100644 --- a/src/snowflake/snowpark/modin/plugin/_internal/ordered_dataframe.py +++ b/src/snowflake/snowpark/modin/plugin/_internal/ordered_dataframe.py @@ -3,7 +3,6 @@ # import logging -import sys import uuid from collections.abc import Hashable from dataclasses import dataclass @@ -41,13 +40,7 @@ from snowflake.snowpark.types import StructType from snowflake.snowpark.window import Window -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from collections.abc import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable _logger = logging.getLogger(__name__) diff --git a/src/snowflake/snowpark/modin/plugin/extensions/indexing_overrides.py b/src/snowflake/snowpark/modin/plugin/extensions/indexing_overrides.py index e645dcc34a..f27acb41bc 100644 --- a/src/snowflake/snowpark/modin/plugin/extensions/indexing_overrides.py +++ b/src/snowflake/snowpark/modin/plugin/extensions/indexing_overrides.py @@ -122,7 +122,7 @@ def is_boolean_array(x: Any) -> bool: """ # special case empty list is not regarded as boolean array; - # because of later Numpy versions (for Python 3.9+), can't + # because of later Numpy versions, can't # compare directly to [], but need workaround to detect list properly if isinstance(x, list) and 0 == len(x): return False diff --git a/src/snowflake/snowpark/modin/plugin/extensions/pd_extensions.py b/src/snowflake/snowpark/modin/plugin/extensions/pd_extensions.py index 486403db7a..049411b846 100644 --- a/src/snowflake/snowpark/modin/plugin/extensions/pd_extensions.py +++ b/src/snowflake/snowpark/modin/plugin/extensions/pd_extensions.py @@ -327,7 +327,7 @@ def _check_obj_and_set_backend_to_snowflake( >>> pd.read_snowflake('''WITH filter_rows AS PROCEDURE (table_name VARCHAR, column_to_filter VARCHAR, value NUMBER) ... RETURNS TABLE(A NUMBER, B NUMBER, C NUMBER) ... LANGUAGE PYTHON - ... RUNTIME_VERSION = '3.9' + ... RUNTIME_VERSION = '3.10' ... PACKAGES = ('snowflake-snowpark-python') ... HANDLER = 'filter_rows' ... AS $$from snowflake.snowpark.functions import col diff --git a/src/snowflake/snowpark/modin/plugin/extensions/utils.py b/src/snowflake/snowpark/modin/plugin/extensions/utils.py index 86d702a90a..6d49a3b6c7 100644 --- a/src/snowflake/snowpark/modin/plugin/extensions/utils.py +++ b/src/snowflake/snowpark/modin/plugin/extensions/utils.py @@ -417,8 +417,8 @@ def validate_and_try_convert_agg_func_arg_func_to_str( allow_duplication: bool Whether allow duplicated function with the same name. Note that numpy functions has different function name compare with the equivalent builtin function, for example, np.min and min have different - names ('amin' and 'min'). However, this behavior is changing with python 3.9, - where np.min will have the same name 'min'. + names ('amin' and 'min'). However, this behavior changed starting with python 3.9, + where np.min has the same name 'min'. axis: int The axis across which the aggregation is applied. diff --git a/src/snowflake/snowpark/row.py b/src/snowflake/snowpark/row.py index d131c01508..d96d02741e 100644 --- a/src/snowflake/snowpark/row.py +++ b/src/snowflake/snowpark/row.py @@ -3,17 +3,9 @@ # Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved. # -import sys +from collections.abc import Iterable from typing import Any, Dict, Union -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable - def _restore_row_from_pickle(values, named_values, fields): if named_values: diff --git a/src/snowflake/snowpark/session.py b/src/snowflake/snowpark/session.py index 457f28f95b..64b7d00470 100644 --- a/src/snowflake/snowpark/session.py +++ b/src/snowflake/snowpark/session.py @@ -242,13 +242,7 @@ import modin.pandas # pragma: no cover from snowflake.snowpark.udf import UserDefinedFunction # pragma: no cover -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable _logger = getLogger(__name__) diff --git a/src/snowflake/snowpark/stored_procedure.py b/src/snowflake/snowpark/stored_procedure.py index 469855def5..6cf33f48d1 100644 --- a/src/snowflake/snowpark/stored_procedure.py +++ b/src/snowflake/snowpark/stored_procedure.py @@ -46,13 +46,7 @@ ) from snowflake.snowpark.types import DataType, StructType -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable class StoredProcedure: diff --git a/src/snowflake/snowpark/table.py b/src/snowflake/snowpark/table.py index 43a53d5945..e93b7227f5 100644 --- a/src/snowflake/snowpark/table.py +++ b/src/snowflake/snowpark/table.py @@ -3,7 +3,6 @@ # Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved. # -import sys import datetime from logging import getLogger from typing import Dict, List, Literal, NamedTuple, Optional, Union, overload @@ -42,13 +41,7 @@ from snowflake.snowpark.row import Row from snowflake.snowpark.types import TimestampTimeZone -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable _logger = getLogger(__name__) diff --git a/src/snowflake/snowpark/types.py b/src/snowflake/snowpark/types.py index 95cdf3a608..a96114b4c7 100644 --- a/src/snowflake/snowpark/types.py +++ b/src/snowflake/snowpark/types.py @@ -27,13 +27,7 @@ # from snowflake.connector.options import installed_pandas, pandas -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable class DataType: diff --git a/src/snowflake/snowpark/udaf.py b/src/snowflake/snowpark/udaf.py index 2a5359a147..d675d574ae 100644 --- a/src/snowflake/snowpark/udaf.py +++ b/src/snowflake/snowpark/udaf.py @@ -44,13 +44,7 @@ from snowflake.snowpark.column import Column from snowflake.snowpark.types import DataType, MapType, StructType -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable class UserDefinedAggregateFunction: diff --git a/src/snowflake/snowpark/udf.py b/src/snowflake/snowpark/udf.py index 9b1e2ea9c9..2d099e5486 100644 --- a/src/snowflake/snowpark/udf.py +++ b/src/snowflake/snowpark/udf.py @@ -54,13 +54,7 @@ from snowflake.snowpark.column import Column from snowflake.snowpark.types import DataType -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable class UserDefinedFunction: diff --git a/src/snowflake/snowpark/udtf.py b/src/snowflake/snowpark/udtf.py index c34644c3af..00a45b1db6 100644 --- a/src/snowflake/snowpark/udtf.py +++ b/src/snowflake/snowpark/udtf.py @@ -59,13 +59,7 @@ from snowflake.snowpark.table_function import TableFunctionCall from snowflake.snowpark.types import DataType, PandasDataFrameType, StructType -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable class UserDefinedTableFunction: diff --git a/src/snowflake/snowpark/window.py b/src/snowflake/snowpark/window.py index 422d06b6cc..f89573ab62 100644 --- a/src/snowflake/snowpark/window.py +++ b/src/snowflake/snowpark/window.py @@ -31,13 +31,7 @@ from snowflake.snowpark._internal.type_utils import ColumnOrName from snowflake.snowpark._internal.utils import parse_positional_args_to_list, publicapi -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable def _convert_boundary_to_expr( From 1fb95e6d4faf29adb7ad50d334cd9afafcee4cf5 Mon Sep 17 00:00:00 2001 From: Jonathan Shi Date: Thu, 7 May 2026 14:23:42 -0700 Subject: [PATCH 2/4] tox files and missed tests --- .pre-commit-config.yaml | 2 +- CONTRIBUTING.md | 6 ++-- README.md | 4 +-- recipe/meta.yaml | 9 ++--- scripts/conda_build.sh | 1 - scripts/conda_build_verification/README.md | 34 +++++++------------ .../conda_build_verification.sh | 6 ++-- .../conda_build_verification/docker_verify.sh | 2 +- setup.py | 5 ++- src/snowflake/snowpark/types.py | 4 +-- tests/README.md | 4 +-- tests/ast/test_ast_driver.py | 4 +-- .../io/test_read_snowflake_query_call.py | 4 +-- .../modin/io/test_read_snowflake_query_cte.py | 2 +- tests/integ/scala/test_udtf_suite.py | 9 +---- tests/integ/test_dataframe.py | 9 +---- tests/integ/test_df_to_snowpark_pandas.py | 6 ++-- tests/integ/test_packaging.py | 4 +-- tests/integ/test_simplifier_suite.py | 9 +---- tests/integ/test_stored_procedure.py | 7 ---- tests/integ/test_stored_procedure_profiler.py | 2 +- tests/integ/test_telemetry.py | 9 +---- tests/integ/test_trace_sql_errors_to_df.py | 2 +- tests/integ/test_udaf.py | 7 ---- tests/integ/test_udf.py | 13 ------- tests/integ/test_udtf.py | 15 +------- tests/perf/perf_runner.py | 9 +---- tests/resources/test_environment.yml | 2 +- tests/unit/test_types.py | 9 +---- tests/unit/test_udtf.py | 8 +---- tox.ini | 12 +++---- 31 files changed, 58 insertions(+), 161 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 54f92ab876..3d2891cba7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ exclude: '^(.*egg.info.*|.*/parameters.py|docs/).*$' default_language_version: - python: python3.9 + python: python3.10 repos: - repo: https://github.com/asottile/pyupgrade rev: v2.31.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e29fddcd58..69d118d44e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,11 +34,11 @@ cd snowpark-python #### Install the library in edit mode and install its dependencies - Create a new Python virtual environment with any Python version that we support. - - The Snowpark Python API supports **Python 3.9, Python 3.10, Python 3.11, Python 3.12 and Python 3.13**. - - The Snowpark pandas API supports **Python 3.9, Python 3.10, and Python 3.11**. Additionally, Snowpark pandas requires **Modin 0.36.x or 0.37.x**, and **pandas 2.2.x or 2.3.x**. + - The Snowpark Python API supports **Python 3.10, Python 3.11, Python 3.12 and Python 3.13**. + - The Snowpark pandas API supports **Python 3.10 and Python 3.11**. Additionally, Snowpark pandas requires **Modin 0.36.x or 0.37.x**, and **pandas 2.2.x or 2.3.x**. ```bash - conda create --name snowpark-dev python=3.9 + conda create --name snowpark-dev python=3.10 ``` - Activate the new Python virtual environment. For example, diff --git a/README.md b/README.md index 08481259d5..3efe15f7bd 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ If you don't have a Snowflake account yet, you can [sign up for a 30-day free tr ### Create a Python virtual environment You can use [miniconda][miniconda], [anaconda][anaconda], or [virtualenv][virtualenv] -to create a Python 3.9, 3.10, 3.11, 3.12 or 3.13 virtual environment. +to create a Python 3.10, 3.11, 3.12 or 3.13 virtual environment. -For Snowpark pandas, only Python 3.9, 3.10, or 3.11 is supported. +For Snowpark pandas, only Python 3.10 or 3.11 is supported. To have the best experience when using it with UDFs, [creating a local conda environment with the Snowflake channel][use snowflake channel] is recommended. diff --git a/recipe/meta.yaml b/recipe/meta.yaml index 6c62dc92f6..a62917c7cd 100644 --- a/recipe/meta.yaml +++ b/recipe/meta.yaml @@ -19,7 +19,6 @@ build: - SNOWFLAKE_IS_PYTHON_RUNTIME_TEST=1 {% if noarch_build %} noarch: python - string: "py39_{{ build_number }}" # [py==39] string: "py310_{{ build_number }}" # [py==310] string: "py311_{{ build_number }}" # [py==311] string: "py312_{{ build_number }}" # [py==312] @@ -27,8 +26,8 @@ build: string: "py314_{{ build_number }}" # [py==314] {% endif %} -{% 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." +{% if noarch_build and py not in [310, 311, 312, 313, 314] %} +error: "Noarch build for Python version {{ py }} is not supported. Supported versions: 3.10, 3.11, 3.12, 3.13, or 3.14." {% else %} requirements: host: @@ -44,9 +43,7 @@ requirements: # mypy-protobuf 3.7.0 requires protobuf >= 5.26 - mypy-protobuf <=3.6.0 run: - {% if noarch_build and py == 39 %} - - python >=3.9,<3.10.0a0 - {% elif noarch_build and py == 310 %} + {% if noarch_build and py == 310 %} - python >=3.10,<3.11.0a0 {% elif noarch_build and py == 311 %} - python >=3.11,<3.12.0a0 diff --git a/scripts/conda_build.sh b/scripts/conda_build.sh index d4b41776ed..e7c2bec920 100755 --- a/scripts/conda_build.sh +++ b/scripts/conda_build.sh @@ -1,5 +1,4 @@ # Used internally by AnacondaPackageBuilder jobs -conda build recipe/ -c sfe1ed40 --python=3.9 --numpy=1.19 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 diff --git a/scripts/conda_build_verification/README.md b/scripts/conda_build_verification/README.md index 81889f7ca3..3d47f9f545 100644 --- a/scripts/conda_build_verification/README.md +++ b/scripts/conda_build_verification/README.md @@ -4,7 +4,7 @@ This directory contains scripts for Docker-based verification of Snowflake Snowp ## Overview -The verification system automatically tests conda packages (.conda and .tar.bz2 formats) for **all Python versions 3.9-3.13** on both x86_64 and aarch64 architectures by: +The verification system automatically tests conda packages (.conda and .tar.bz2 formats) for **all Python versions 3.10-3.13** on both x86_64 and aarch64 architectures by: 1. Scanning the `package/` directory for conda packages in `linux-64`, `linux-aarch64`, and `noarch` subdirectories 2. Launching Docker containers using `continuumio/miniconda3` (supports both architectures) @@ -27,20 +27,17 @@ The verification expects packages to be organized as follows: ``` package/ ├── linux-64/ # x86_64 packages -│ ├── snowflake-snowpark-python-1.38.0-py39_0.conda │ ├── snowflake-snowpark-python-1.38.0-py310_0.conda │ ├── snowflake-snowpark-python-1.38.0-py311_0.conda │ ├── snowflake-snowpark-python-1.38.0-py312_0.conda │ ├── snowflake-snowpark-python-1.38.0-py313_0.conda │ └── ... (corresponding .tar.bz2 files) ├── linux-aarch64/ # aarch64 packages -│ ├── snowflake-snowpark-python-1.38.0-py39_0.conda │ ├── snowflake-snowpark-python-1.38.0-py310_0.conda -│ └── ... (all Python versions 3.9-3.13) +│ └── ... (all Python versions 3.10-3.13) └── noarch/ # Architecture-independent packages - ├── snowflake-snowpark-python-1.39.0-py39_0.conda ├── snowflake-snowpark-python-1.39.0-py310_0.conda - └── ... (all Python versions 3.9-3.13) + └── ... (all Python versions 3.10-3.13) ``` ## Usage @@ -50,7 +47,7 @@ package/ **Prerequisites**: Ensure you have created `parameters.py` with valid Snowflake connection parameters (see Required Setup section above). ```bash -# Test all Python versions (3.9-3.13) on noarch only +# Test all Python versions (3.10-3.13) on noarch only ./conda_build_verification.sh # Test all Python versions on specific architecture(s) @@ -105,10 +102,10 @@ Then run verification: - `architecture...` (optional): One or more architecture directories to test - Available: `linux-64`, `linux-aarch64`, `noarch` - Default: `noarch` only if no architectures specified -- **Python versions**: Automatically tests all Python versions 3.9-3.13 +- **Python versions**: Automatically tests all Python versions 3.10-3.13 **Examples:** -- `./conda_build_verification.sh` → Test all Python versions (3.9-3.13) on noarch +- `./conda_build_verification.sh` → Test all Python versions (3.10-3.13) on noarch - `./conda_build_verification.sh linux-64` → Test all Python versions on linux-64 only - `./conda_build_verification.sh linux-64 noarch` → Test all Python versions on both linux-64 and noarch - `./conda_build_verification.sh linux-64 linux-aarch64 noarch` → Test all Python versions on all architectures @@ -131,7 +128,7 @@ Then run verification: - Validates `parameters.py` file existence and provides helpful error messages if missing - Fails fast before any Docker operations begin -2. **Package Discovery**: Scans `package/linux-64`, `package/linux-aarch64`, and `package/noarch` for packages across all Python versions (3.9-3.13) +2. **Package Discovery**: Scans `package/linux-64`, `package/linux-aarch64`, and `package/noarch` for packages across all Python versions (3.10-3.13) - **Strict Validation**: If packages are not found in any requested architecture directory, the script errors out immediately 3. **Docker Container Launch**: For each architecture with packages: @@ -140,7 +137,7 @@ Then run verification: - Mounts verification scripts and package directory - Runs `docker_verify.sh` inside the container -4. **Package Testing**: Inside each container, for each Python version (3.9-3.13): +4. **Package Testing**: Inside each container, for each Python version (3.10-3.13): - Creates isolated conda environment for the specific Python version - Installs package dependencies - Installs the conda package (.conda and .tar.bz2 formats) if available for that version @@ -168,7 +165,7 @@ Docker uses the `--platform` flag to specify the target architecture: ## Example Output ``` -Testing Python versions: 3.9, 3.10, 3.11, 3.12, 3.13 +Testing Python versions: 3.10, 3.11, 3.12, 3.13 Using default architecture: noarch Testing architectures: noarch Script directory: /path/to/scripts/conda_build_verification @@ -178,16 +175,9 @@ GPG_KEY found, attempting to decrypt parameters.py... Starting Docker-based conda package verification... Found packages in noarch === Running verification for noarch === -Found packages in noarch, will test all Python versions (3.9-3.13) +Found packages in noarch, will test all Python versions (3.10-3.13) Running Docker command for noarch... -=== Testing Python 3.9 === -Testing conda package: snowflake-snowpark-python-1.39.0-py39_0.conda -✅ Package test completed successfully: snowflake-snowpark-python-1.39.0-py39_0.conda -Testing tar.bz2 package: snowflake-snowpark-python-1.39.0-py39_0.tar.bz2 -✅ Package test completed successfully: snowflake-snowpark-python-1.39.0-py39_0.tar.bz2 -✅ All tests passed for Python 3.9 - === Testing Python 3.10 === Testing conda package: snowflake-snowpark-python-1.39.0-py310_0.conda ✅ Package test completed successfully: snowflake-snowpark-python-1.39.0-py310_0.conda @@ -225,7 +215,7 @@ Removed decrypted parameters.py ``` This error occurs when the script cannot find packages for a requested architecture directory. - **To fix**: Ensure packages are in the correct `package/` subdirectories with proper naming patterns (e.g., `snowflake-snowpark-python-*-py39_*.conda`), or use one of the available architectures listed in the error message. + **To fix**: Ensure packages are in the correct `package/` subdirectories with proper naming patterns (e.g., `snowflake-snowpark-python-*-py310_*.conda`), or use one of the available architectures listed in the error message. 3. **GPG decryption fails** ``` @@ -294,7 +284,7 @@ docker run -it --rm \ continuumio/miniconda3:latest \ bash -# Inside container, run the verification script (tests all Python versions 3.9-3.13) +# Inside container, run the verification script (tests all Python versions 3.10-3.13) bash /verification/docker_verify.sh ``` diff --git a/scripts/conda_build_verification/conda_build_verification.sh b/scripts/conda_build_verification/conda_build_verification.sh index 6f502a3c0c..ab9d6e1908 100755 --- a/scripts/conda_build_verification/conda_build_verification.sh +++ b/scripts/conda_build_verification/conda_build_verification.sh @@ -2,7 +2,7 @@ # Docker-based Snowflake Snowpark Python conda package verification # Supports both x86_64 and aarch64 architectures using continuumio/miniconda3 -# Tests all Python versions (3.9-3.13) automatically +# Tests all Python versions (3.10-3.13) automatically # # Usage: ./conda_build_verification.sh [architecture...] # @@ -27,7 +27,7 @@ fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -echo "Testing Python versions: 3.9, 3.10, 3.11, 3.12, 3.13" +echo "Testing Python versions: 3.10, 3.11, 3.12, 3.13" echo "Testing architectures: ${PACKAGE_DIRS[*]}" echo "Script directory: $SCRIPT_DIR" echo "Project root: $PROJECT_ROOT" @@ -81,7 +81,7 @@ run_docker_verification() { return 0 fi - echo "Found packages in $arch_dir, will test all Python versions (3.9-3.13)" + echo "Found packages in $arch_dir, will test all Python versions (3.10-3.13)" # Create container name local container_name="snowpark-verify-${arch_dir}-$(date +%s)" diff --git a/scripts/conda_build_verification/docker_verify.sh b/scripts/conda_build_verification/docker_verify.sh index 4e3cef39cb..6a364d5acb 100755 --- a/scripts/conda_build_verification/docker_verify.sh +++ b/scripts/conda_build_verification/docker_verify.sh @@ -6,7 +6,7 @@ set -e # List of Python versions to test -PYTHON_VERSIONS=("3.9" "3.10" "3.11" "3.12" "3.13") +PYTHON_VERSIONS=("3.10" "3.11" "3.12" "3.13") echo "=== Docker Container Verification Script ===" echo "Python versions to test: ${PYTHON_VERSIONS[*]}" diff --git a/setup.py b/setup.py index a3e3699e8f..5ec404ce68 100644 --- a/setup.py +++ b/setup.py @@ -32,10 +32,10 @@ "python-dateutil", # Snowpark IR "tzlocal", # Snowpark IR ] -REQUIRED_PYTHON_VERSION = ">=3.9, <3.15" +REQUIRED_PYTHON_VERSION = ">=3.10, <3.15" if os.getenv("SNOWFLAKE_IS_PYTHON_RUNTIME_TEST", False): - REQUIRED_PYTHON_VERSION = ">=3.9" + REQUIRED_PYTHON_VERSION = ">=3.10" PANDAS_REQUIREMENTS = [ f"snowflake-connector-python[pandas]{CONNECTOR_DEPENDENCY_VERSION}", @@ -246,7 +246,6 @@ def run(self): "Operating System :: OS Independent", "Programming Language :: SQL", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/src/snowflake/snowpark/types.py b/src/snowflake/snowpark/types.py index a96114b4c7..3d97816615 100644 --- a/src/snowflake/snowpark/types.py +++ b/src/snowflake/snowpark/types.py @@ -1214,8 +1214,8 @@ def _parse_datatype_json_value(json_value: Union[dict, str]) -> DataType: # TODO(SNOW-969479): Add a type hint that can be used to annotate Vector data. Python does not # currently support integer type parameters (which are needed to represent a vector's dimension). -# typing.Annotate can be used as a temporary bypass once the minimum supported Python version is -# bumped to 3.9 +# typing.Annotated can be used as a temporary bypass now that the minimum supported Python version +# is 3.10 #: The type hint for annotating TIMESTAMP_NTZ (e.g., ``Timestamp[NTZ]``) data when registering UDFs. NTZ = TypeVar("NTZ") diff --git a/tests/README.md b/tests/README.md index 434739d41c..dac2602a86 100644 --- a/tests/README.md +++ b/tests/README.md @@ -55,8 +55,8 @@ your web browser. This will be located in `/.tox/htmlcov` To just run the all Snowpark tests, run the following command: ```bash -# Must have Python 3.9 for the tests to run -python -m tox -e py39 +# Must have Python 3.10 for the tests to run +python -m tox -e py310 ``` ### Running doctests diff --git a/tests/ast/test_ast_driver.py b/tests/ast/test_ast_driver.py index 88d2c6b215..6eceed83d5 100644 --- a/tests/ast/test_ast_driver.py +++ b/tests/ast/test_ast_driver.py @@ -96,8 +96,8 @@ def load_test_cases(): """ test_files = DATA_DIR.glob("*.test") major_version, minor_version = sys.version_info[0], sys.version_info[1] - if major_version == 3 and minor_version < 9: - # Remove the `to_snowpark_pandas` test since Snowpark pandas is only supported in Python 3.9+. + if major_version == 3 and minor_version < 10: + # Remove the `to_snowpark_pandas` test since Snowpark pandas is only supported in Python 3.10+. test_files = filter( lambda file: "to_snowpark_pandas" not in file.name, test_files ) diff --git a/tests/integ/modin/io/test_read_snowflake_query_call.py b/tests/integ/modin/io/test_read_snowflake_query_call.py index e32da418a9..5bed5d396b 100644 --- a/tests/integ/modin/io/test_read_snowflake_query_call.py +++ b/tests/integ/modin/io/test_read_snowflake_query_call.py @@ -37,7 +37,7 @@ def test_read_snowflake_call_sproc(session, enforce_ordering): CREATE OR REPLACE PROCEDURE filter_by_role(tableName VARCHAR, role VARCHAR) RETURNS TABLE(id NUMBER, name VARCHAR, role VARCHAR) LANGUAGE PYTHON - RUNTIME_VERSION = '3.9' + RUNTIME_VERSION = '3.10' PACKAGES = ('snowflake-snowpark-python') HANDLER = 'filter_by_role' AS $$from snowflake.snowpark.functions import col @@ -70,7 +70,7 @@ def test_read_snowflake_call_sproc_enforce_ordering_neg(session): CREATE OR REPLACE PROCEDURE filter_by_role(tableName VARCHAR, role VARCHAR) RETURNS TABLE(id NUMBER, name VARCHAR, role VARCHAR) LANGUAGE PYTHON - RUNTIME_VERSION = '3.9' + RUNTIME_VERSION = '3.10' PACKAGES = ('snowflake-snowpark-python') HANDLER = 'filter_by_role' AS $$from snowflake.snowpark.functions import col diff --git a/tests/integ/modin/io/test_read_snowflake_query_cte.py b/tests/integ/modin/io/test_read_snowflake_query_cte.py index e94aa8d168..682b77a7d2 100644 --- a/tests/integ/modin/io/test_read_snowflake_query_cte.py +++ b/tests/integ/modin/io/test_read_snowflake_query_cte.py @@ -219,7 +219,7 @@ def test_read_snowflake_query_cte_with_python_anonymous_sproc( WITH filterByRole AS PROCEDURE (tableName VARCHAR, role VARCHAR) RETURNS TABLE("id" NUMBER, "name" VARCHAR, "role" VARCHAR) LANGUAGE PYTHON - RUNTIME_VERSION = '3.9' + RUNTIME_VERSION = '3.10' PACKAGES = ('snowflake-snowpark-python') HANDLER = 'filter_by_role' AS $$from snowflake.snowpark.functions import col diff --git a/tests/integ/scala/test_udtf_suite.py b/tests/integ/scala/test_udtf_suite.py index 0b00c09d96..3b338dfc89 100644 --- a/tests/integ/scala/test_udtf_suite.py +++ b/tests/integ/scala/test_udtf_suite.py @@ -4,7 +4,6 @@ import datetime import decimal -import sys from collections import Counter from typing import Dict, List, Tuple @@ -26,13 +25,7 @@ from tests.utils import Utils -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable pytestmark = [ pytest.mark.udf, diff --git a/tests/integ/test_dataframe.py b/tests/integ/test_dataframe.py index dc069c2cb2..18aedcac80 100644 --- a/tests/integ/test_dataframe.py +++ b/tests/integ/test_dataframe.py @@ -9,7 +9,6 @@ import json import logging import math -import sys import time from array import array from collections import namedtuple @@ -112,13 +111,7 @@ structured_types_supported, ) -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable tmp_stage_name = Utils.random_stage_name() test_file_on_stage = f"@{tmp_stage_name}/testCSV.csv" diff --git a/tests/integ/test_df_to_snowpark_pandas.py b/tests/integ/test_df_to_snowpark_pandas.py index 6c181120f6..3fc0911f7d 100644 --- a/tests/integ/test_df_to_snowpark_pandas.py +++ b/tests/integ/test_df_to_snowpark_pandas.py @@ -51,11 +51,11 @@ def test_to_snowpark_pandas_no_modin(session, tmp_table_basic, enforce_ordering) try: import modin # noqa: F401 except ModuleNotFoundError: - if sys.version_info.major == 3 and sys.version_info.minor == 8: - # Snowpark pandas does not support Python 3.8 + if sys.version_info.major == 3 and sys.version_info.minor <= 9: + # Snowpark pandas does not support Python 3.9 or earlier ctx = pytest.raises( RuntimeError, - match="Snowpark pandas does not support Python 3.8. Please update to Python 3.9 or later", + match="Snowpark pandas does not support Python 3.9 or earlier. Please update to Python 3.10 or later", ) else: # This function call will raise a ModuleNotFoundError since modin is not installed diff --git a/tests/integ/test_packaging.py b/tests/integ/test_packaging.py index da7a0fbf0d..466bbc040f 100644 --- a/tests/integ/test_packaging.py +++ b/tests/integ/test_packaging.py @@ -142,7 +142,7 @@ def ranged_yaml_file(): - conda-forge - defaults dependencies: # List of packages and versions to include in the environment - - python=3.9 # Python version + - python=3.10 # Python version - numpy<=1.24.3 """ @@ -815,7 +815,7 @@ def test_add_requirements_yaml(session, resources_path): "seaborn", "scipy", } - assert session._runtime_version_from_requirement == "3.9" + assert session._runtime_version_from_requirement == "3.10" udf_name = Utils.random_name_for_temp_object(TempObjectType.FUNCTION) system_version = f"{sys.version_info[0]}.{sys.version_info[1]}" diff --git a/tests/integ/test_simplifier_suite.py b/tests/integ/test_simplifier_suite.py index b446347d51..91147e540d 100644 --- a/tests/integ/test_simplifier_suite.py +++ b/tests/integ/test_simplifier_suite.py @@ -3,7 +3,6 @@ # import itertools -import sys import time import re from typing import Tuple @@ -35,13 +34,7 @@ ) from tests.utils import TestData, Utils -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable pytestmark = [ pytest.mark.xfail( diff --git a/tests/integ/test_stored_procedure.py b/tests/integ/test_stored_procedure.py index eef6752dd8..a370bc3fba 100644 --- a/tests/integ/test_stored_procedure.py +++ b/tests/integ/test_stored_procedure.py @@ -8,7 +8,6 @@ import logging import os import re -import sys import time from typing import Dict, List, Optional, Union from unittest.mock import patch @@ -2314,9 +2313,6 @@ def test_register_sproc_after_switch_schema(session): IS_IN_STORED_PROC, reason="Stored proc env does not have permissions to look up warehouse details", ) -@pytest.mark.skipif( - sys.version_info < (3, 9), reason="artifact repository requires Python 3.9+" -) def test_sproc_artifact_repository(session): def artifact_repo_test(_): import urllib3 @@ -2406,9 +2402,6 @@ def test_func(x): reason="artifact repository not supported in local testing", ) @pytest.mark.skipif(IS_NOT_ON_GITHUB, reason="need resources") -@pytest.mark.skipif( - sys.version_info < (3, 9), reason="artifact repository requires Python 3.9+" -) @pytest.mark.skip("SNOW-2362946: Skip until root cause is found.") def test_sproc_artifact_repository_from_file(session, tmpdir): source = dedent( diff --git a/tests/integ/test_stored_procedure_profiler.py b/tests/integ/test_stored_procedure_profiler.py index 5d715b022a..0c14b4acbd 100644 --- a/tests/integ/test_stored_procedure_profiler.py +++ b/tests/integ/test_stored_procedure_profiler.py @@ -174,7 +174,7 @@ def test_set_incorrect_active_profiler( """WITH myProcedure AS PROCEDURE () RETURNS TABLE ( ) LANGUAGE PYTHON - RUNTIME_VERSION = '3.9' + RUNTIME_VERSION = '3.10' PACKAGES = ( 'snowflake-snowpark-python==1.2.0', 'pandas==1.3.3' ) IMPORTS = ( '@my_stage/file1.py', '@my_stage/file2.py' ) HANDLER = 'my_function' diff --git a/tests/integ/test_telemetry.py b/tests/integ/test_telemetry.py index 5ba186f239..0e8b216c75 100644 --- a/tests/integ/test_telemetry.py +++ b/tests/integ/test_telemetry.py @@ -4,7 +4,6 @@ # import decimal -import sys import threading from unittest.mock import patch import uuid @@ -43,13 +42,7 @@ from snowflake.snowpark.types import IntegerType from tests.utils import TestData, TestFiles -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable pytestmark = [ pytest.mark.xfail( diff --git a/tests/integ/test_trace_sql_errors_to_df.py b/tests/integ/test_trace_sql_errors_to_df.py index 73cf32cbfc..5dd8dcfec8 100644 --- a/tests/integ/test_trace_sql_errors_to_df.py +++ b/tests/integ/test_trace_sql_errors_to_df.py @@ -24,7 +24,7 @@ ), pytest.mark.skipif( sys.version_info < (3, 11), - reason="Line numbers are flaky in Python 3.9", + reason="Line numbers are flaky before Python 3.11", run=False, ), ] diff --git a/tests/integ/test_udaf.py b/tests/integ/test_udaf.py index d56585b7ef..79a75da294 100644 --- a/tests/integ/test_udaf.py +++ b/tests/integ/test_udaf.py @@ -5,7 +5,6 @@ import datetime import decimal import os -import sys from textwrap import dedent from typing import Any, Dict, List @@ -636,9 +635,6 @@ def finish(self): IS_IN_STORED_PROC, reason="Stored proc env does not have permissions to look up warehouse details", ) -@pytest.mark.skipif( - sys.version_info < (3, 9), reason="artifact repository requires Python 3.9+" -) def test_udaf_artifact_repository(session): class ArtifactRepositoryHandler: def __init__(self) -> None: @@ -702,9 +698,6 @@ def finish(self): reason="artifact repository not supported in local testing", ) @pytest.mark.skipif(IS_NOT_ON_GITHUB, reason="need resources") -@pytest.mark.skipif( - sys.version_info < (3, 9), reason="artifact repository requires Python 3.9+" -) def test_udaf_artifact_repository_from_file(session, tmpdir): source = dedent( """ diff --git a/tests/integ/test_udf.py b/tests/integ/test_udf.py index f64516b1d4..9c284f0273 100644 --- a/tests/integ/test_udf.py +++ b/tests/integ/test_udf.py @@ -10,7 +10,6 @@ import math import os import re -import sys from textwrap import dedent from typing import Callable @@ -2946,9 +2945,6 @@ def handler(): reason="artifact repository not supported in local testing", ) @pytest.mark.skipif(IS_NOT_ON_GITHUB, reason="need resources") -@pytest.mark.skipif( - sys.version_info < (3, 9), reason="artifact repository requires Python 3.9+" -) def test_register_artifact_repository(session): def test_urllib() -> str: import urllib3 @@ -3018,9 +3014,6 @@ def test_func(x): IS_IN_STORED_PROC, reason="Stored proc env does not have permissions to look up warehouse details", ) -@pytest.mark.skipif( - sys.version_info < (3, 9), reason="artifact repository requires Python 3.9+" -) def test_register_artifact_repository_negative(session): def test_nop() -> str: pass @@ -3078,9 +3071,6 @@ def test_nop() -> str: reason="artifact repository not supported in local testing", ) @pytest.mark.skipif(IS_NOT_ON_GITHUB, reason="need resources") -@pytest.mark.skipif( - sys.version_info < (3, 9), reason="artifact repository requires Python 3.9+" -) def test_udf_artifact_repository_from_file(session, tmpdir): source = dedent( """ @@ -3111,9 +3101,6 @@ def test_urllib() -> str: ) @pytest.mark.skipif(IS_IN_STORED_PROC, reason="Cannot create session in SP") @pytest.mark.skipif(IS_NOT_ON_GITHUB, reason="need resources") -@pytest.mark.skipif( - sys.version_info < (3, 9), reason="artifact repository requires Python 3.9+" -) def test_use_default_artifact_repository(db_parameters): with create_session_for_test(db_parameters) as session: temp_database = Utils.random_temp_database() diff --git a/tests/integ/test_udtf.py b/tests/integ/test_udtf.py index 577022f582..277ce29ae8 100644 --- a/tests/integ/test_udtf.py +++ b/tests/integ/test_udtf.py @@ -5,7 +5,6 @@ import datetime import decimal import os -import sys from textwrap import dedent from typing import Dict, List, Tuple from unittest import mock @@ -33,13 +32,7 @@ from tests.integ.session_parameters import create_session_for_test from tests.utils import IS_IN_STORED_PROC, IS_NOT_ON_GITHUB, TestFiles, Utils -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable try: import pandas as pd @@ -1509,9 +1502,6 @@ def process( IS_IN_STORED_PROC, reason="Stored proc env does not have permissions to look up warehouse details", ) -@pytest.mark.skipif( - sys.version_info < (3, 9), reason="artifact repository requires Python 3.9+" -) def test_udtf_artifact_repository(session, resources_path): class ArtifactRepositoryUDTF: def process(self) -> Iterable[Tuple[str]]: @@ -1567,9 +1557,6 @@ def process(self) -> Iterable[Tuple[str]]: reason="artifact repository not supported in local testing", ) @pytest.mark.skipif(IS_NOT_ON_GITHUB, reason="need resources") -@pytest.mark.skipif( - sys.version_info < (3, 9), reason="artifact repository requires Python 3.9+" -) def test_udtf_artifact_repository_from_file(session, tmpdir): source = dedent( """ diff --git a/tests/perf/perf_runner.py b/tests/perf/perf_runner.py index 86d62176c4..a0a15b72d4 100644 --- a/tests/perf/perf_runner.py +++ b/tests/perf/perf_runner.py @@ -3,6 +3,7 @@ # import argparse +from collections.abc import Iterable import random import sys import time @@ -17,14 +18,6 @@ sys.path.append(connection_parameters_path) from parameters import CONNECTION_PARAMETERS # noqa: E402 -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable - def generate_columns(n: int) -> List[str]: return [f'{i} as {"a" * 50}{i}' for i in range(n)] diff --git a/tests/resources/test_environment.yml b/tests/resources/test_environment.yml index d60993d0b9..3b764d7e3d 100644 --- a/tests/resources/test_environment.yml +++ b/tests/resources/test_environment.yml @@ -5,7 +5,7 @@ channels: # List of Conda channels to use for package installation - defaults dependencies: # List of packages and versions to include in the environment - - python=3.9.21 # Python version + - python=3.10.0 # Python version - numpy=1.24.3 - pandas - scikit-learn=0.24.2 diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index 0f9a064ad7..6f2f8cf0ff 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -5,7 +5,6 @@ import decimal import os -import sys import typing from array import array from collections import defaultdict @@ -96,13 +95,7 @@ ) from tests.utils import IS_WINDOWS, TestFiles -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable resources_path = os.path.normpath( os.path.join(os.path.dirname(__file__), "../resources") diff --git a/tests/unit/test_udtf.py b/tests/unit/test_udtf.py index e6f3585641..b038907153 100644 --- a/tests/unit/test_udtf.py +++ b/tests/unit/test_udtf.py @@ -20,13 +20,7 @@ from snowflake.snowpark.functions import udtf from snowflake.snowpark.udtf import UDTFRegistration -# Python 3.8 needs to use typing.Iterable because collections.abc.Iterable is not subscriptable -# Python 3.9 can use both -# Python 3.10 needs to use collections.abc.Iterable because typing.Iterable is removed -if sys.version_info <= (3, 9): - from typing import Iterable -else: - from collections.abc import Iterable +from collections.abc import Iterable @mock.patch("snowflake.snowpark.udtf.cleanup_failed_permanent_registration") diff --git a/tox.ini b/tox.ini index fec74cd6a1..ac6df6350d 100644 --- a/tox.ini +++ b/tox.ini @@ -33,7 +33,7 @@ show_contexts = true [tox] minversion = 3.7 envlist = fix_lint, - py39, + py310, coverage nopandas skip_missing_interpreters = true @@ -141,7 +141,7 @@ commands = snowparkpandasnotdoctest: bash scripts/run_snowparkpandasnotdoctest.sh {env:SNOW_1314507_WORKAROUND_RERUN_FLAGS} {posargs:} # This one only run doctest but we still need to include the tests folder to let tests/conftest.py to mark the doctest files for us snowparkpandasdoctest: {env:MODIN_PYTEST_CMD} --durations=20 -m "{env:SNOWFLAKE_TEST_TYPE}" {posargs:} {env:SNOW_1314507_WORKAROUND_RERUN_FLAGS} src/snowflake/snowpark/modin/ tests/unit/modin - # This one is used by daily_modin_precommit_py39_py310.yml + # This one is used by daily_modin_precommit_py310.yml snowparkpandasdailynotdoctest: {env:MODIN_PYTEST_DAILY_CMD} --durations=20 -m "{env:SNOWFLAKE_TEST_TYPE}" {posargs:} {env:SNOW_1314507_WORKAROUND_RERUN_FLAGS} tests/unit/modin tests/integ/modin tests/integ/test_df_to_snowpark_pandas.py # This one is only called by jenkins job and the only difference from `snowparkpandasnotdoctest` is that it uses # MODIN_PYTEST_NO_COV_CMD instead of MODIN_PYTEST_CMD @@ -182,10 +182,10 @@ 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, py314 +depends = py310, py311, py312, py313, py314 [testenv:docs] -basepython = python3.9 +basepython = python3.10 description = build docs for the project skip_install = false deps = @@ -205,7 +205,7 @@ commands = flake8 {posargs} [testenv:fix_lint] allowlist_externals = bash description = format the code base to adhere to our styles, and complain about what we cannot do automatically -basepython = python3.9 +basepython = python3.10 passenv = PROGRAMDATA deps = @@ -243,7 +243,7 @@ commands = python -m pip list --format=columns [testenv:snowpark_pandas_modin_pandas_import_error] deps = .[modin-development] description = test error messages when importing unsupported modin or pandas versions -basepython = python3.9 +basepython = python3.10 setenv = TEST_INCORRECT_MODIN_PANDAS_VERSIONS=True commands = python -m pytest --noconftest tests/unit/modin/test_pandas_version.py From f6a08a3b23c0d2ae10e7db0b5a97861ec90e4e14 Mon Sep 17 00:00:00 2001 From: Jonathan Shi Date: Thu, 7 May 2026 14:24:38 -0700 Subject: [PATCH 3/4] changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c123085d81..5f19c63214 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ #### Bug Fixes +- Fixed a bug where using parameter bindings for `CALL` queries issued through `session.sql` would raise an error. +- Fixed a bug where `StringType` columns from Iceberg tables were not recognized as max-size strings. +- Improved the `FileNotFoundError` message raised when `INFER_SCHEMA` returns zero rows so it also points to file format options (`PARSE_HEADER`, `SKIP_HEADER`, `ON_ERROR=CONTINUE`) that can silently filter everything out, instead of only suggesting a missing path. + +#### Deprecations + +- Removed support for Python 3.9. + #### Improvements - When `Session.reduce_describe_query_enabled` is enabled, fewer DESCRIBE queries are issued when the outer query only projects or renames columns from an inner subquery whose column types are already known. From e8b72d5f1330298bef8e2376a5a799a991641dae Mon Sep 17 00:00:00 2001 From: Jonathan Shi Date: Fri, 8 May 2026 14:59:14 -0700 Subject: [PATCH 4/4] update fips install version --- ci/docker/snowpark_test_fips/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/docker/snowpark_test_fips/Dockerfile b/ci/docker/snowpark_test_fips/Dockerfile index d8d7305f8f..f417f1ae56 100644 --- a/ci/docker/snowpark_test_fips/Dockerfile +++ b/ci/docker/snowpark_test_fips/Dockerfile @@ -19,7 +19,7 @@ RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo && \ RUN yum clean all && \ yum groupinstall -y "Development Tools" && \ yum install -y redhat-rpm-config gcc libffi-devel wget && \ - yum install -y perl-IPC-Cmd perl-Digest-SHA perl-Test-Simple perl-Pod-Html python39 python39-devel && \ + yum install -y perl-IPC-Cmd perl-Digest-SHA perl-Test-Simple perl-Pod-Html python310 python310-devel && \ yum clean all && \ rm -rf /var/cache/yum