From fbaec77eac8f15faa0f3650897dbb24a8ca40494 Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Thu, 6 Aug 2026 15:46:35 +0000 Subject: [PATCH 01/13] SNOW-2912540: add IS_V5_DRIVER constant for version-conditioned connector imports connector_version is already imported in utils.py; this one-liner exposes a boolean flag so callers can gate imports or behavior that differs between the legacy connector (v3/v4) and the Universal Driver (v5+). Co-Authored-By: Claude Sonnet 4.6 --- src/snowflake/snowpark/_internal/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/snowflake/snowpark/_internal/utils.py b/src/snowflake/snowpark/_internal/utils.py index 99dc105b37..109f5ac422 100644 --- a/src/snowflake/snowpark/_internal/utils.py +++ b/src/snowflake/snowpark/_internal/utils.py @@ -367,6 +367,8 @@ def is_interactive() -> bool: return hasattr(sys, "ps1") or sys.flags.interactive or "snowbook" in sys.modules +IS_V5_DRIVER: bool = connector_version[0] >= 5 + @lru_cache def get_connector_version() -> str: return ".".join([str(d) for d in connector_version if d is not None]) From 2ebb35ddfcbe3c2105b0bfe36a0b76ab6a703dfc Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Wed, 12 Aug 2026 16:19:15 +0000 Subject: [PATCH 02/13] SNOW-2912540: define MissingOptionalDependency/pandas/pyarrow locally; drop connector.options imports snowflake.connector.options is a backward-compat shim in v5 (UD) that will eventually be removed. Define MissingOptionalDependency, MissingPandas, MissingPyarrow, ModuleLikeObject, pandas, pyarrow, installed_pandas, and installed_pyarrow directly in _internal/utils.py and redirect all thirteen source-file imports there. Co-Authored-By: Claude Sonnet 4.6 --- .../_internal/analyzer/analyzer_utils.py | 2 +- .../data_source/datasource_reader.py | 2 +- .../data_source/drivers/base_driver.py | 2 +- .../_internal/event_table_telemetry.py | 7 +++- .../snowpark/_internal/server_connection.py | 2 +- .../snowpark/_internal/type_utils.py | 2 +- src/snowflake/snowpark/_internal/udf_utils.py | 2 +- src/snowflake/snowpark/_internal/utils.py | 38 +++++++++++++++---- src/snowflake/snowpark/async_job.py | 2 +- src/snowflake/snowpark/dataframe.py | 2 +- src/snowflake/snowpark/mock/_options.py | 2 +- .../snowpark/relational_grouped_dataframe.py | 2 +- src/snowflake/snowpark/session.py | 2 +- 13 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py b/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py index 09511b201d..e3641da32c 100644 --- a/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py +++ b/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py @@ -13,7 +13,7 @@ from snowflake.connector import ProgrammingError from snowflake.connector.cursor import SnowflakeCursor -from snowflake.connector.options import pyarrow +from snowflake.snowpark._internal.utils import pyarrow from snowflake.snowpark._internal.analyzer.binary_plan_node import ( AsOf, Except, diff --git a/src/snowflake/snowpark/_internal/data_source/datasource_reader.py b/src/snowflake/snowpark/_internal/data_source/datasource_reader.py index de72d40f86..c8f568b508 100644 --- a/src/snowflake/snowpark/_internal/data_source/datasource_reader.py +++ b/src/snowflake/snowpark/_internal/data_source/datasource_reader.py @@ -11,7 +11,7 @@ from snowflake.snowpark._internal.data_source.drivers.base_driver import BaseDriver from snowflake.snowpark.exceptions import SnowparkDataframeReaderException from snowflake.snowpark.types import StructType -from snowflake.connector.options import pandas as pd +from snowflake.snowpark._internal.utils import pandas as pd import logging logger = logging.getLogger(__name__) diff --git a/src/snowflake/snowpark/_internal/data_source/drivers/base_driver.py b/src/snowflake/snowpark/_internal/data_source/drivers/base_driver.py index 88a65e8825..3cf154764e 100644 --- a/src/snowflake/snowpark/_internal/data_source/drivers/base_driver.py +++ b/src/snowflake/snowpark/_internal/data_source/drivers/base_driver.py @@ -4,7 +4,7 @@ from enum import Enum import datetime from typing import Dict, List, Callable, Any, Optional, TYPE_CHECKING -from snowflake.connector.options import pandas as pd +from snowflake.snowpark._internal.utils import pandas as pd from snowflake.snowpark._internal.analyzer.analyzer_utils import unquote_if_quoted from snowflake.snowpark._internal.data_source.datasource_typing import ( diff --git a/src/snowflake/snowpark/_internal/event_table_telemetry.py b/src/snowflake/snowpark/_internal/event_table_telemetry.py index a1b70b7e4d..3a5a494c6d 100644 --- a/src/snowflake/snowpark/_internal/event_table_telemetry.py +++ b/src/snowflake/snowpark/_internal/event_table_telemetry.py @@ -8,11 +8,14 @@ from abc import ABC from logging import getLogger from typing import Dict, Optional, Tuple -from snowflake.connector.options import MissingOptionalDependency, ModuleLikeObject import snowflake.snowpark import requests -from snowflake.snowpark._internal.utils import parse_table_name +from snowflake.snowpark._internal.utils import ( + MissingOptionalDependency, + ModuleLikeObject, + parse_table_name, +) _logger = getLogger(__name__) diff --git a/src/snowflake/snowpark/_internal/server_connection.py b/src/snowflake/snowpark/_internal/server_connection.py index 94df8ceb9c..aae3f71b07 100644 --- a/src/snowflake/snowpark/_internal/server_connection.py +++ b/src/snowflake/snowpark/_internal/server_connection.py @@ -29,7 +29,7 @@ from snowflake.connector.cursor import ResultMetadata, SnowflakeCursor from snowflake.connector.errors import Error, NotSupportedError, ProgrammingError from snowflake.connector.network import ReauthenticationRequest -from snowflake.connector.options import pandas +from snowflake.snowpark._internal.utils import pandas from snowflake.snowpark._internal.analyzer.analyzer_utils import ( quote_name_without_upper_casing, ) diff --git a/src/snowflake/snowpark/_internal/type_utils.py b/src/snowflake/snowpark/_internal/type_utils.py index c7ca2a2a3e..d790cae476 100644 --- a/src/snowflake/snowpark/_internal/type_utils.py +++ b/src/snowflake/snowpark/_internal/type_utils.py @@ -36,7 +36,7 @@ import snowflake.snowpark.types # type: ignore from snowflake.connector.constants import FIELD_ID_TO_NAME from snowflake.connector.cursor import ResultMetadata -from snowflake.connector.options import installed_pandas, pandas +from snowflake.snowpark._internal.utils import installed_pandas, pandas from snowflake.snowpark._internal.utils import quote_name from snowflake.snowpark.row import Row from snowflake.snowpark.types import ( diff --git a/src/snowflake/snowpark/_internal/udf_utils.py b/src/snowflake/snowpark/_internal/udf_utils.py index 7b3b947904..7beb5e4369 100644 --- a/src/snowflake/snowpark/_internal/udf_utils.py +++ b/src/snowflake/snowpark/_internal/udf_utils.py @@ -29,7 +29,7 @@ from packaging.requirements import Requirement import snowflake.snowpark -from snowflake.connector.options import installed_pandas, pandas +from snowflake.snowpark._internal.utils import installed_pandas, pandas from snowflake.snowpark._internal import code_generation, type_utils from snowflake.snowpark._internal.analyzer.datatype_mapper import to_sql, to_sql_no_cast from snowflake.snowpark._internal.telemetry import TelemetryField diff --git a/src/snowflake/snowpark/_internal/utils.py b/src/snowflake/snowpark/_internal/utils.py index 109f5ac422..ba481f094b 100644 --- a/src/snowflake/snowpark/_internal/utils.py +++ b/src/snowflake/snowpark/_internal/utils.py @@ -25,6 +25,7 @@ import traceback import uuid import zipfile +from types import ModuleType from enum import Enum, IntEnum, auto, unique from functools import lru_cache, wraps from itertools import count @@ -54,9 +55,23 @@ from snowflake.connector.constants import FIELD_ID_TO_NAME from snowflake.connector.cursor import ResultMetadata, SnowflakeCursor from snowflake.connector.description import OPERATING_SYSTEM, PLATFORM -from snowflake.connector.options import MissingOptionalDependency, ModuleLikeObject +from snowflake.connector.errors import MissingDependencyError from snowflake.connector.version import VERSION as connector_version from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages + + +class MissingOptionalDependency: + _dep_name = "not set" + + def __getattr__(self, item: str) -> None: + raise MissingDependencyError(self._dep_name) + + +class MissingPandas(MissingOptionalDependency): + _dep_name = "pandas" + + +ModuleLikeObject = Union[ModuleType, MissingOptionalDependency] from snowflake.snowpark.row import Row from snowflake.snowpark.version import VERSION as snowpark_version @@ -240,25 +255,34 @@ SUPPORTED_TABLE_TYPES = ["temp", "temporary", "transient"] -# TODO: merge fixed pandas importer changes to connector. def _pandas_importer(): # noqa: E302 """Helper function to lazily import pandas and return MissingPandas if not installed.""" - from snowflake.connector.options import MissingPandas - - pandas = MissingPandas() + result = MissingPandas() try: - pandas = importlib.import_module("pandas") + result = importlib.import_module("pandas") # since we enable relative imports without dots this import gives us an issues when ran from test directory from pandas import DataFrame # NOQA except ImportError: # pragma: no cover pass # pragma: no cover - return pandas + return result pandas = _pandas_importer() installed_pandas = not isinstance(pandas, MissingOptionalDependency) +class MissingPyarrow(MissingOptionalDependency): + _dep_name = "pyarrow" + + +try: + pyarrow = importlib.import_module("pyarrow") +except ImportError: # pragma: no cover + pyarrow = MissingPyarrow() # type: ignore[assignment] + +installed_pyarrow: bool = not isinstance(pyarrow, MissingOptionalDependency) + + class TempObjectType(Enum): TABLE = "TABLE" VIEW = "VIEW" diff --git a/src/snowflake/snowpark/async_job.py b/src/snowflake/snowpark/async_job.py index 7bae2324fc..b18732150b 100644 --- a/src/snowflake/snowpark/async_job.py +++ b/src/snowflake/snowpark/async_job.py @@ -8,7 +8,7 @@ import snowflake.snowpark from snowflake.connector.errors import DatabaseError -from snowflake.connector.options import pandas +from snowflake.snowpark._internal.utils import pandas from snowflake.snowpark._internal.analyzer.analyzer_utils import result_scan_statement from snowflake.snowpark._internal.analyzer.snowflake_plan import Query from snowflake.snowpark._internal.utils import ( diff --git a/src/snowflake/snowpark/dataframe.py b/src/snowflake/snowpark/dataframe.py index 42e7b4ef65..76e77169eb 100644 --- a/src/snowflake/snowpark/dataframe.py +++ b/src/snowflake/snowpark/dataframe.py @@ -31,7 +31,7 @@ import snowflake.snowpark import snowflake.snowpark.context as context import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto -from snowflake.connector.options import installed_pandas, pandas, pyarrow +from snowflake.snowpark._internal.utils import installed_pandas, pandas, pyarrow from snowflake.snowpark._internal.analyzer.binary_plan_node import ( AsOf, diff --git a/src/snowflake/snowpark/mock/_options.py b/src/snowflake/snowpark/mock/_options.py index 7ae10dcda5..ebd2cd7456 100644 --- a/src/snowflake/snowpark/mock/_options.py +++ b/src/snowflake/snowpark/mock/_options.py @@ -4,7 +4,7 @@ import importlib -from snowflake.connector.options import MissingOptionalDependency, MissingPandas +from snowflake.snowpark._internal.utils import MissingOptionalDependency, MissingPandas try: import pandas diff --git a/src/snowflake/snowpark/relational_grouped_dataframe.py b/src/snowflake/snowpark/relational_grouped_dataframe.py index 189d37d66d..8b427ebbd1 100644 --- a/src/snowflake/snowpark/relational_grouped_dataframe.py +++ b/src/snowflake/snowpark/relational_grouped_dataframe.py @@ -8,7 +8,7 @@ from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto import snowflake.snowpark.context as context -from snowflake.connector.options import pandas +from snowflake.snowpark._internal.utils import pandas from snowflake.snowpark._internal.analyzer.analyzer_utils import unquote_if_quoted from snowflake.snowpark import functions from snowflake.snowpark._internal.analyzer.expression import ( diff --git a/src/snowflake/snowpark/session.py b/src/snowflake/snowpark/session.py index aea7b7e5de..7484504054 100644 --- a/src/snowflake/snowpark/session.py +++ b/src/snowflake/snowpark/session.py @@ -42,7 +42,7 @@ import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto import snowflake.snowpark.context as context from snowflake.connector import ProgrammingError, SnowflakeConnection -from snowflake.connector.options import installed_pandas, pandas, pyarrow +from snowflake.snowpark._internal.utils import installed_pandas, pandas, pyarrow from snowflake.connector.pandas_tools import write_pandas from snowflake.snowpark import UDFProfiler From 9128d49cd622d7a64834b82b4769ad2ea8f8281b Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Wed, 12 Aug 2026 20:25:51 +0000 Subject: [PATCH 03/13] SNOW-2912540: use IS_V5_DRIVER for TelemetryData import path The v5 (Universal Driver) public snowflake.connector.telemetry shim does not always expose TelemetryData.TRUE/.FALSE (present on _internal.telemetry in some UD builds), which made every telemetry-sending Snowpark test raise AttributeError: type object 'TelemetryData' has no attribute 'FALSE'. Gate the TelemetryData import on IS_V5_DRIVER: on v5 prefer _internal.telemetry and fall back to the public shim; on v4 keep the legacy public import. Co-authored-by: Cursor --- src/snowflake/snowpark/_internal/telemetry.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/snowflake/snowpark/_internal/telemetry.py b/src/snowflake/snowpark/_internal/telemetry.py index 80f16d395e..da2453220b 100644 --- a/src/snowflake/snowpark/_internal/telemetry.py +++ b/src/snowflake/snowpark/_internal/telemetry.py @@ -14,7 +14,6 @@ from snowflake.connector import SnowflakeConnection from snowflake.connector.telemetry import ( TelemetryClient as PCTelemetryClient, - TelemetryData as PCTelemetryData, TelemetryField as PCTelemetryField, ) from snowflake.connector.time_util import get_time_millis @@ -30,6 +29,7 @@ DescribeQueryTelemetryField, ) from snowflake.snowpark._internal.utils import ( + IS_V5_DRIVER, get_application_name, get_os_name, get_python_version, @@ -39,6 +39,20 @@ generate_random_alphanumeric, ) +if IS_V5_DRIVER: + # The v5 (Universal Driver) public ``snowflake.connector.telemetry`` shim does + # not always expose ``TelemetryData.TRUE`` / ``.FALSE``; on some UD builds those + # constants live on the ``_internal.telemetry`` module instead. Prefer that + # module and fall back to the public shim (later UD builds restore them there). + try: + from snowflake.connector._internal.telemetry import ( + TelemetryData as PCTelemetryData, + ) + except ImportError: # pragma: no cover + from snowflake.connector.telemetry import TelemetryData as PCTelemetryData +else: + from snowflake.connector.telemetry import TelemetryData as PCTelemetryData + try: import psutil From 7c5908b5a14af09248216c2b2b2e56d4966bbb3e Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Fri, 14 Aug 2026 16:54:02 +0000 Subject: [PATCH 04/13] SNOW-2912540: revert local MissingOptionalDependency/pandas/pyarrow redefinition; re-source from connector 04c82d2fa assumed connector.options had a gap and redefined MissingOptionalDependency, MissingPandas, MissingPyarrow, ModuleLikeObject, pyarrow, and installed_pyarrow locally. connector.options already provides all of these except installed_pyarrow (verified against the actually-installed v4.7.2 connector, which only exposes pyarrow itself and couples its availability to pandas's import tuple, not a standalone name). Re-import the four names that do exist there, gated on IS_V5_DRIVER so this keeps working once UD's connector._common.extras lands, and derive installed_pyarrow locally via isinstance instead of maintaining an independent, unprecedented MissingPyarrow resolution. pandas/installed_pandas (produced by the pre-existing _pandas_importer(), unrelated to 04c82d2fa) are intentionally left untouched -- unifying those is a separate follow-up. --- src/snowflake/snowpark/_internal/utils.py | 46 ++++++++++------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/src/snowflake/snowpark/_internal/utils.py b/src/snowflake/snowpark/_internal/utils.py index ba481f094b..a68708a0fe 100644 --- a/src/snowflake/snowpark/_internal/utils.py +++ b/src/snowflake/snowpark/_internal/utils.py @@ -25,7 +25,6 @@ import traceback import uuid import zipfile -from types import ModuleType from enum import Enum, IntEnum, auto, unique from functools import lru_cache, wraps from itertools import count @@ -55,23 +54,30 @@ from snowflake.connector.constants import FIELD_ID_TO_NAME from snowflake.connector.cursor import ResultMetadata, SnowflakeCursor from snowflake.connector.description import OPERATING_SYSTEM, PLATFORM -from snowflake.connector.errors import MissingDependencyError from snowflake.connector.version import VERSION as connector_version from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages +IS_V5_DRIVER: bool = connector_version[0] >= 5 -class MissingOptionalDependency: - _dep_name = "not set" - - def __getattr__(self, item: str) -> None: - raise MissingDependencyError(self._dep_name) - - -class MissingPandas(MissingOptionalDependency): - _dep_name = "pandas" - +if IS_V5_DRIVER: + from snowflake.connector._common.extras import ( + MissingOptionalDependency, + MissingPandas, + ModuleLikeObject, + pyarrow, + ) +else: + from snowflake.connector.options import ( + MissingOptionalDependency, + MissingPandas, + ModuleLikeObject, + pyarrow, + ) -ModuleLikeObject = Union[ModuleType, MissingOptionalDependency] +# connector.options (v4) never exported installed_pyarrow as its own name -- pyarrow's +# availability is only implicit in the pandas/pyarrow import tuple -- so this is derived +# locally the same way for both driver generations rather than assuming the name exists. +installed_pyarrow: bool = not isinstance(pyarrow, MissingOptionalDependency) from snowflake.snowpark.row import Row from snowflake.snowpark.version import VERSION as snowpark_version @@ -271,18 +277,6 @@ def _pandas_importer(): # noqa: E302 installed_pandas = not isinstance(pandas, MissingOptionalDependency) -class MissingPyarrow(MissingOptionalDependency): - _dep_name = "pyarrow" - - -try: - pyarrow = importlib.import_module("pyarrow") -except ImportError: # pragma: no cover - pyarrow = MissingPyarrow() # type: ignore[assignment] - -installed_pyarrow: bool = not isinstance(pyarrow, MissingOptionalDependency) - - class TempObjectType(Enum): TABLE = "TABLE" VIEW = "VIEW" @@ -391,8 +385,6 @@ def is_interactive() -> bool: return hasattr(sys, "ps1") or sys.flags.interactive or "snowbook" in sys.modules -IS_V5_DRIVER: bool = connector_version[0] >= 5 - @lru_cache def get_connector_version() -> str: return ".".join([str(d) for d in connector_version if d is not None]) From e5d47881dc9abe0b575645c90eeae7e77bb77a19 Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Sat, 15 Aug 2026 11:35:07 +0000 Subject: [PATCH 05/13] SNOW-2912540: fix MissingPandas import to match UD PR #1151's now-current shape UD PR #1151 was updated since cb9f590f2 landed: MissingPandas is deleted outright from _common/extras.py (BehaviorDifferences.yaml #66), not kept as a deprecated re-export. Its suggested replacement, MissingOptionalDependency("pandas"), only works on v5 -- the real v4 connector's MissingOptionalDependency defines no __init__ override, so it only supports the no-arg-subclass pattern. Import MissingPandas only where it's real (v4). Add _missing_pandas() to build the sentinel with whichever construction style the active driver generation supports, centralizing the branch in _internal/utils.py rather than spreading IS_V5_DRIVER awareness to callers. Fix _pandas_importer() and mock/_options.py, both of which referenced MissingPandas directly and would otherwise NameError/ ImportError under IS_V5_DRIVER=True. installed_pyarrow also moves to a direct v5 import: #1151 fixed it to check pyarrow independently instead of mirroring installed_pandas, so it's now correct to import there instead of re-deriving locally. v4 still doesn't export it at all, so the local isinstance derivation stays there. --- src/snowflake/snowpark/_internal/utils.py | 20 ++++++++++++++------ src/snowflake/snowpark/mock/_options.py | 4 ++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/snowflake/snowpark/_internal/utils.py b/src/snowflake/snowpark/_internal/utils.py index a68708a0fe..b61330783e 100644 --- a/src/snowflake/snowpark/_internal/utils.py +++ b/src/snowflake/snowpark/_internal/utils.py @@ -62,9 +62,9 @@ if IS_V5_DRIVER: from snowflake.connector._common.extras import ( MissingOptionalDependency, - MissingPandas, ModuleLikeObject, pyarrow, + installed_pyarrow, ) else: from snowflake.connector.options import ( @@ -74,10 +74,18 @@ pyarrow, ) -# connector.options (v4) never exported installed_pyarrow as its own name -- pyarrow's -# availability is only implicit in the pandas/pyarrow import tuple -- so this is derived -# locally the same way for both driver generations rather than assuming the name exists. -installed_pyarrow: bool = not isinstance(pyarrow, MissingOptionalDependency) + # connector.options (v4) never exported installed_pyarrow as its own name. + installed_pyarrow: bool = not isinstance(pyarrow, MissingOptionalDependency) + + +def _missing_pandas() -> MissingOptionalDependency: + # v4 has no __init__ override (no-arg-subclass only); v5 deleted + # MissingPandas in favor of the positional-arg form. + if IS_V5_DRIVER: + return MissingOptionalDependency("pandas") + return MissingPandas() + + from snowflake.snowpark.row import Row from snowflake.snowpark.version import VERSION as snowpark_version @@ -263,7 +271,7 @@ def _pandas_importer(): # noqa: E302 """Helper function to lazily import pandas and return MissingPandas if not installed.""" - result = MissingPandas() + result = _missing_pandas() try: result = importlib.import_module("pandas") # since we enable relative imports without dots this import gives us an issues when ran from test directory diff --git a/src/snowflake/snowpark/mock/_options.py b/src/snowflake/snowpark/mock/_options.py index ebd2cd7456..7d1f088433 100644 --- a/src/snowflake/snowpark/mock/_options.py +++ b/src/snowflake/snowpark/mock/_options.py @@ -4,14 +4,14 @@ import importlib -from snowflake.snowpark._internal.utils import MissingOptionalDependency, MissingPandas +from snowflake.snowpark._internal.utils import MissingOptionalDependency, _missing_pandas try: import pandas installed_pandas = True except ImportError: - pandas = MissingPandas() + pandas = _missing_pandas() installed_pandas = False From 564be06fa55f7545bbe3b30967cea91a21bfcb61 Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Sat, 15 Aug 2026 11:57:38 +0000 Subject: [PATCH 06/13] SNOW-2912540: fix TelemetryData import to a real, verified location on v5 The previous try/except hedged between two locations, neither of which actually has TelemetryData.TRUE/.FALSE on the current UD main: the public snowflake.connector.telemetry shim's TelemetryData has no TRUE/FALSE at all, and _internal.telemetry currently has no TelemetryData class either. The fallback branch was silently reachable and silently wrong. UD PR #1106 (open, not draft) adds TelemetryData/TelemetryField to _internal/telemetry.py specifically to match Snowpark's exact usage (PCTelemetryData(message=..., timestamp=...), .TRUE/.FALSE) -- confirmed by reading its actual diff. Import from there unconditionally on v5, no try/except: both branches now import from one definite, verified location. --- src/snowflake/snowpark/_internal/telemetry.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/snowflake/snowpark/_internal/telemetry.py b/src/snowflake/snowpark/_internal/telemetry.py index da2453220b..5912ce054a 100644 --- a/src/snowflake/snowpark/_internal/telemetry.py +++ b/src/snowflake/snowpark/_internal/telemetry.py @@ -40,16 +40,9 @@ ) if IS_V5_DRIVER: - # The v5 (Universal Driver) public ``snowflake.connector.telemetry`` shim does - # not always expose ``TelemetryData.TRUE`` / ``.FALSE``; on some UD builds those - # constants live on the ``_internal.telemetry`` module instead. Prefer that - # module and fall back to the public shim (later UD builds restore them there). - try: - from snowflake.connector._internal.telemetry import ( - TelemetryData as PCTelemetryData, - ) - except ImportError: # pragma: no cover - from snowflake.connector.telemetry import TelemetryData as PCTelemetryData + from snowflake.connector._internal.telemetry import ( + TelemetryData as PCTelemetryData, + ) else: from snowflake.connector.telemetry import TelemetryData as PCTelemetryData From b079d8576ffc89fa1efe95183f268669dc731919 Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Sat, 15 Aug 2026 11:39:02 +0000 Subject: [PATCH 07/13] SNOW-2912540: remove _pandas_importer(), source pandas/installed_pandas from connector _pandas_importer() predates this whole effort and duplicated resolution the connector already does correctly on both driver generations -- including the "relative imports without dots" DataFrame workaround, now folded into UD's own _common.extras.pandas (confirmed on the not-yet-merged UD PR #1151/#1152; v4's options.py already had it). Add pandas/installed_pandas to the existing IS_V5_DRIVER-gated import block and delete the local resolution entirely. Verified the workaround isn't needed on Snowpark's side by running the exact invocation style its comment called out (pytest with tests/unit/ as cwd) -- no failure, consistent with both driver generations now handling it internally. --- src/snowflake/snowpark/_internal/utils.py | 20 ++++---------------- tests/unit/test_internal_utils.py | 12 ------------ 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/snowflake/snowpark/_internal/utils.py b/src/snowflake/snowpark/_internal/utils.py index b61330783e..5578477ab6 100644 --- a/src/snowflake/snowpark/_internal/utils.py +++ b/src/snowflake/snowpark/_internal/utils.py @@ -63,7 +63,9 @@ from snowflake.connector._common.extras import ( MissingOptionalDependency, ModuleLikeObject, + pandas, pyarrow, + installed_pandas, installed_pyarrow, ) else: @@ -71,7 +73,9 @@ MissingOptionalDependency, MissingPandas, ModuleLikeObject, + pandas, pyarrow, + installed_pandas, ) # connector.options (v4) never exported installed_pyarrow as its own name. @@ -269,22 +273,6 @@ def _missing_pandas() -> MissingOptionalDependency: SUPPORTED_TABLE_TYPES = ["temp", "temporary", "transient"] -def _pandas_importer(): # noqa: E302 - """Helper function to lazily import pandas and return MissingPandas if not installed.""" - result = _missing_pandas() - try: - result = importlib.import_module("pandas") - # since we enable relative imports without dots this import gives us an issues when ran from test directory - from pandas import DataFrame # NOQA - except ImportError: # pragma: no cover - pass # pragma: no cover - return result - - -pandas = _pandas_importer() -installed_pandas = not isinstance(pandas, MissingOptionalDependency) - - class TempObjectType(Enum): TABLE = "TABLE" VIEW = "VIEW" diff --git a/tests/unit/test_internal_utils.py b/tests/unit/test_internal_utils.py index f09cfc63d3..e3e6882dc5 100644 --- a/tests/unit/test_internal_utils.py +++ b/tests/unit/test_internal_utils.py @@ -4,11 +4,9 @@ import concurrent.futures import random import pytest -from snowflake.connector.options import MissingPandas from snowflake.snowpark._internal import utils from snowflake.snowpark._internal.utils import ( - _pandas_importer, generate_random_alphanumeric, split_snowflake_identifier_with_dot, ) @@ -148,16 +146,6 @@ def test_normalize_path_escapes_backslash_and_quote(raw_path, is_local): ), f"decoded={decoded!r} does not end with {expected_tail!r}" -def test__pandas_importer(): - imported_pandas = _pandas_importer() - try: - import pandas - - assert imported_pandas == pandas - except ImportError: - assert isinstance(imported_pandas, MissingPandas) - - def test_generate_random_alphanumeric(): random.seed(42) random_string1 = generate_random_alphanumeric() From c7d4c600a25873c1dd9f35ee93c0bdece0dfeacf Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Sat, 15 Aug 2026 11:41:08 +0000 Subject: [PATCH 08/13] SNOW-2912540: dedup mock/_options.py's numpy handling against _common.extras mock/_options.py's MissingNumpy/numpy try-except was functionally identical to _common/extras.py's own numpy resolution (confirmed: pure duplicate, no fix to merge, per UD PR #1152's investigation). Import numpy from _common.extras on v5; v4 keeps its own MissingNumpy class since v4's options.py has no numpy handling to delegate to. Does not touch the pandas try/except in this file -- Local Testing deliberately never resolves pyarrow, unlike every other pandas-resolution path in this codebase (commit #1628). --- src/snowflake/snowpark/mock/_options.py | 29 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/snowflake/snowpark/mock/_options.py b/src/snowflake/snowpark/mock/_options.py index 7d1f088433..37d71e3d4f 100644 --- a/src/snowflake/snowpark/mock/_options.py +++ b/src/snowflake/snowpark/mock/_options.py @@ -4,7 +4,11 @@ import importlib -from snowflake.snowpark._internal.utils import MissingOptionalDependency, _missing_pandas +from snowflake.snowpark._internal.utils import ( + IS_V5_DRIVER, + MissingOptionalDependency, + _missing_pandas, +) try: import pandas @@ -15,15 +19,20 @@ installed_pandas = False -class MissingNumpy(MissingOptionalDependency): - """The class is specifically for numpy optional dependency.""" +if IS_V5_DRIVER: + from snowflake.connector._common.extras import numpy - _dep_name = "numpy" + installed_numpy = not isinstance(numpy, MissingOptionalDependency) +else: + class MissingNumpy(MissingOptionalDependency): + """The class is specifically for numpy optional dependency.""" -try: - numpy = importlib.import_module("numpy") - installed_numpy = True -except ImportError: - numpy = MissingNumpy() - installed_numpy = False + _dep_name = "numpy" + + try: + numpy = importlib.import_module("numpy") + installed_numpy = True + except ImportError: + numpy = MissingNumpy() + installed_numpy = False From 90fd4793553813940405ab89a5b9acc1be7bd2e5 Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Mon, 17 Aug 2026 19:00:04 +0000 Subject: [PATCH 09/13] SNOW-2912540: fix black formatting after removing _pandas_importer() Removing the function left only one blank line before class TempObjectType; black requires two before a top-level class definition. --- src/snowflake/snowpark/_internal/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/snowflake/snowpark/_internal/utils.py b/src/snowflake/snowpark/_internal/utils.py index 5578477ab6..351526a1f2 100644 --- a/src/snowflake/snowpark/_internal/utils.py +++ b/src/snowflake/snowpark/_internal/utils.py @@ -273,6 +273,7 @@ def _missing_pandas() -> MissingOptionalDependency: SUPPORTED_TABLE_TYPES = ["temp", "temporary", "transient"] + class TempObjectType(Enum): TABLE = "TABLE" VIEW = "VIEW" From 8460b3b0dc9e824501fdd92d741f52d03e9301c2 Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Mon, 17 Aug 2026 19:09:32 +0000 Subject: [PATCH 10/13] SNOW-2912540: fix flake8 F401/E402 in utils.py's IS_V5_DRIVER import block - F401: pandas is imported purely for other modules to re-import from here, so it's never referenced elsewhere in this file. Split into its own import with a noqa, rather than noqa-ing a name inside a multi-line parenthesized import (which flake8 attributes to the opening line, not the name's own line). - E402: the IS_V5_DRIVER conditional-import block and _missing_pandas() ended up sitting between the top-of-file imports and two later ones (Row, VERSION). Moved those two imports up to stay contiguous. --- src/snowflake/snowpark/_internal/utils.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/snowflake/snowpark/_internal/utils.py b/src/snowflake/snowpark/_internal/utils.py index 351526a1f2..6f75b52c63 100644 --- a/src/snowflake/snowpark/_internal/utils.py +++ b/src/snowflake/snowpark/_internal/utils.py @@ -56,24 +56,26 @@ from snowflake.connector.description import OPERATING_SYSTEM, PLATFORM from snowflake.connector.version import VERSION as connector_version from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages +from snowflake.snowpark.row import Row +from snowflake.snowpark.version import VERSION as snowpark_version IS_V5_DRIVER: bool = connector_version[0] >= 5 if IS_V5_DRIVER: + from snowflake.connector._common.extras import pandas # noqa: F401 from snowflake.connector._common.extras import ( MissingOptionalDependency, ModuleLikeObject, - pandas, pyarrow, installed_pandas, installed_pyarrow, ) else: + from snowflake.connector.options import pandas # noqa: F401 from snowflake.connector.options import ( MissingOptionalDependency, MissingPandas, ModuleLikeObject, - pandas, pyarrow, installed_pandas, ) @@ -90,9 +92,6 @@ def _missing_pandas() -> MissingOptionalDependency: return MissingPandas() -from snowflake.snowpark.row import Row -from snowflake.snowpark.version import VERSION as snowpark_version - if TYPE_CHECKING: from snowflake.snowpark._internal.analyzer.snowflake_plan import ( SnowflakePlan, From 2befb01ed536b259d3ad93c2c968023848b7d855 Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Fri, 21 Aug 2026 06:55:02 +0000 Subject: [PATCH 11/13] SNOW-2912540: gate ReauthenticationRequest/TelemetryClient/TelemetryField imports on IS_V5_DRIVER network.py doesn't exist in UD at all -- legacy's errors.py/network.py split is consolidated into errors.py. UD PR #1224 (open, stacked on #1133) adds ReauthenticationRequest(ProgrammingError) to errors.py and removes network.py outright, naming Snowpark's import site explicitly as the target. Gate the import in server_connection.py and its unit test mock, same pattern as every other IS_V5_DRIVER import in this stack. TelemetryClient/TelemetryField were imported unconditionally from the top-level snowflake.connector.telemetry module, which is a stub on v5 (the real implementation lives in _common.telemetry per UD PR #1106's current branch). Also fixes this same file's PCTelemetryData import, added in an earlier commit against _internal.telemetry -- that class moved to _common too on the same #1106 branch since that commit landed, so it was already stale for the identical reason. Adds test coverage for _missing_pandas() (added earlier in this stack to replace direct MissingPandas() construction), which had zero coverage after test__pandas_importer() was deleted alongside _pandas_importer() itself. --- .../snowpark/_internal/server_connection.py | 7 ++++++- src/snowflake/snowpark/_internal/telemetry.py | 14 ++++++++------ tests/unit/test_internal_utils.py | 6 ++++++ tests/unit/test_server_connection.py | 7 ++++++- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/snowflake/snowpark/_internal/server_connection.py b/src/snowflake/snowpark/_internal/server_connection.py index aae3f71b07..ed025ba2e8 100644 --- a/src/snowflake/snowpark/_internal/server_connection.py +++ b/src/snowflake/snowpark/_internal/server_connection.py @@ -28,7 +28,6 @@ from snowflake.connector.constants import FIELD_ID_TO_NAME from snowflake.connector.cursor import ResultMetadata, SnowflakeCursor from snowflake.connector.errors import Error, NotSupportedError, ProgrammingError -from snowflake.connector.network import ReauthenticationRequest from snowflake.snowpark._internal.utils import pandas from snowflake.snowpark._internal.analyzer.analyzer_utils import ( quote_name_without_upper_casing, @@ -54,6 +53,7 @@ get_plan_telemetry_metrics, ) from snowflake.snowpark._internal.utils import ( + IS_V5_DRIVER, create_rlock, create_thread_local, escape_quotes, @@ -69,6 +69,11 @@ result_set_to_rows, unwrap_stage_location_single_quote, ) + +if IS_V5_DRIVER: + from snowflake.connector.errors import ReauthenticationRequest +else: + from snowflake.connector.network import ReauthenticationRequest from snowflake.snowpark import context from snowflake.snowpark.async_job import AsyncJob, _AsyncResultType from snowflake.snowpark.query_history import QueryListener, QueryRecord diff --git a/src/snowflake/snowpark/_internal/telemetry.py b/src/snowflake/snowpark/_internal/telemetry.py index 5912ce054a..4d012df803 100644 --- a/src/snowflake/snowpark/_internal/telemetry.py +++ b/src/snowflake/snowpark/_internal/telemetry.py @@ -12,10 +12,6 @@ from typing import Any, Dict, List, Optional from snowflake.connector import SnowflakeConnection -from snowflake.connector.telemetry import ( - TelemetryClient as PCTelemetryClient, - TelemetryField as PCTelemetryField, -) from snowflake.connector.time_util import get_time_millis from snowflake.snowpark._internal.analyzer.query_plan_analysis_utils import ( PlanState, @@ -40,11 +36,17 @@ ) if IS_V5_DRIVER: - from snowflake.connector._internal.telemetry import ( + from snowflake.connector._common.telemetry import ( + TelemetryClient as PCTelemetryClient, TelemetryData as PCTelemetryData, + TelemetryField as PCTelemetryField, ) else: - from snowflake.connector.telemetry import TelemetryData as PCTelemetryData + from snowflake.connector.telemetry import ( + TelemetryClient as PCTelemetryClient, + TelemetryData as PCTelemetryData, + TelemetryField as PCTelemetryField, + ) try: import psutil diff --git a/tests/unit/test_internal_utils.py b/tests/unit/test_internal_utils.py index e3e6882dc5..97843ab42d 100644 --- a/tests/unit/test_internal_utils.py +++ b/tests/unit/test_internal_utils.py @@ -198,3 +198,9 @@ def test_generate_random_alphanumeric(): ) def test_split_snowflake_identifier_with_dot(string, expected_result): assert split_snowflake_identifier_with_dot(string) == expected_result + + +def test_missing_pandas(): + result = utils._missing_pandas() + assert isinstance(result, utils.MissingOptionalDependency) + assert result._dep_name == "pandas" diff --git a/tests/unit/test_server_connection.py b/tests/unit/test_server_connection.py index a53d42dc16..f2a3761f5e 100644 --- a/tests/unit/test_server_connection.py +++ b/tests/unit/test_server_connection.py @@ -10,9 +10,14 @@ import pytest -from snowflake.connector.network import ReauthenticationRequest from snowflake.snowpark import Session from snowflake.snowpark._internal.analyzer.snowflake_plan import Query, SnowflakePlan +from snowflake.snowpark._internal.utils import IS_V5_DRIVER + +if IS_V5_DRIVER: + from snowflake.connector.errors import ReauthenticationRequest +else: + from snowflake.connector.network import ReauthenticationRequest from snowflake.snowpark.exceptions import ( SnowparkFetchDataException, SnowparkQueryCancelledException, From 205b00ea2b2c311fca8ca7e7d09929b2a06bca87 Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Fri, 21 Aug 2026 07:13:29 +0000 Subject: [PATCH 12/13] SNOW-2912540: fix 4 integ test files' unconditional connector.options import from snowflake.connector.options import installed_pandas is unconditional in tests/integ/test_function.py, test_cte.py, scala/test_datatype_suite.py, and scala/test_update_delete_merge_suite.py -- ModuleNotFoundError once UD deletes options.py outright (confirmed: f61156c7a, ancestor of SNOW-2912540-extras-to-common's current tip, already relied on elsewhere in this stack). Swap to snowflake.snowpark._internal.utils, which already re-exports installed_pandas correctly gated on IS_V5_DRIVER internally (from this PR's earlier _pandas_importer()-removal commit) -- no IS_V5_DRIVER awareness needed in these test files themselves. --- tests/integ/scala/test_datatype_suite.py | 2 +- tests/integ/scala/test_update_delete_merge_suite.py | 2 +- tests/integ/test_cte.py | 2 +- tests/integ/test_function.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integ/scala/test_datatype_suite.py b/tests/integ/scala/test_datatype_suite.py index 950e05a060..0bed68f20e 100644 --- a/tests/integ/scala/test_datatype_suite.py +++ b/tests/integ/scala/test_datatype_suite.py @@ -12,7 +12,7 @@ from unittest import mock import snowflake.snowpark.context as context -from snowflake.connector.options import installed_pandas +from snowflake.snowpark._internal.utils import installed_pandas from snowflake.snowpark import Row from snowflake.snowpark.dataframe import DataFrame from snowflake.snowpark.exceptions import SnowparkSQLException diff --git a/tests/integ/scala/test_update_delete_merge_suite.py b/tests/integ/scala/test_update_delete_merge_suite.py index ed5e904aff..1c45d701d3 100644 --- a/tests/integ/scala/test_update_delete_merge_suite.py +++ b/tests/integ/scala/test_update_delete_merge_suite.py @@ -8,7 +8,7 @@ import pytest -from snowflake.connector.options import installed_pandas +from snowflake.snowpark._internal.utils import installed_pandas from snowflake.snowpark import ( DeleteResult, MergeResult, diff --git a/tests/integ/test_cte.py b/tests/integ/test_cte.py index fcb9a6694a..966a50bd8e 100644 --- a/tests/integ/test_cte.py +++ b/tests/integ/test_cte.py @@ -11,7 +11,7 @@ import pytest from snowflake.connector.errors import ProgrammingError -from snowflake.connector.options import installed_pandas +from snowflake.snowpark._internal.utils import installed_pandas from snowflake.snowpark import Window from snowflake.snowpark._internal.analyzer import analyzer from snowflake.snowpark._internal.analyzer.snowflake_plan import PlanQueryType, Query diff --git a/tests/integ/test_function.py b/tests/integ/test_function.py index 455707e8bf..e32dfa636b 100644 --- a/tests/integ/test_function.py +++ b/tests/integ/test_function.py @@ -165,7 +165,7 @@ year, ) import functools -from snowflake.connector.options import installed_pandas +from snowflake.snowpark._internal.utils import installed_pandas from snowflake.snowpark.functions import udf, vectorized from snowflake.snowpark.udf import UserDefinedFunction from snowflake.snowpark.types import ( From 6568b8e46490f11f501c3ee37beb57fdedbdbc8e Mon Sep 17 00:00:00 2001 From: Filip Pawlowski Date: Fri, 21 Aug 2026 07:37:33 +0000 Subject: [PATCH 13/13] SNOW-2912540: extract options-related imports to new _internal/options.py _internal/utils.py's IS_V5_DRIVER-gated block (MissingOptionalDependency, ModuleLikeObject, pandas, pyarrow, installed_pandas, installed_pyarrow, _missing_pandas()) was a self-contained concern mirroring the connector's own options.py/_common.extras 1:1, buried in an already-large kitchen-sink file. Moved to a dedicated module, mirroring mock/_options.py's existing role as the scoped equivalent for the local-testing side. options.py computes its own IS_V5_DRIVER independently rather than importing it from utils.py, since utils.py itself needs names back from options.py (MissingOptionalDependency/ModuleLikeObject/installed_pandas, used by its modin-optional-dependency code) -- importing in both directions would be circular. One-line duplication, avoids import-order fragility entirely. Updated all 17 consumers (found types.py's multi-line import via a regex-based sweep after a naive single-line grep missed it) to import these names from _internal.options instead. mock/_options.py and event_table_telemetry.py keep their other _internal.utils imports (IS_V5_DRIVER, parse_table_name) unchanged. --- .../_internal/analyzer/analyzer_utils.py | 2 +- .../data_source/datasource_reader.py | 2 +- .../data_source/drivers/base_driver.py | 2 +- .../_internal/event_table_telemetry.py | 4 +- src/snowflake/snowpark/_internal/options.py | 38 +++++++++++++++++++ .../snowpark/_internal/server_connection.py | 2 +- .../snowpark/_internal/type_utils.py | 2 +- src/snowflake/snowpark/_internal/udf_utils.py | 2 +- src/snowflake/snowpark/_internal/utils.py | 34 +++-------------- src/snowflake/snowpark/async_job.py | 2 +- src/snowflake/snowpark/dataframe.py | 2 +- src/snowflake/snowpark/mock/_options.py | 4 +- .../snowpark/relational_grouped_dataframe.py | 2 +- src/snowflake/snowpark/session.py | 2 +- src/snowflake/snowpark/types.py | 7 +--- tests/integ/scala/test_datatype_suite.py | 2 +- .../scala/test_update_delete_merge_suite.py | 2 +- tests/integ/test_cte.py | 2 +- tests/integ/test_function.py | 2 +- tests/unit/test_internal_utils.py | 6 +-- 20 files changed, 66 insertions(+), 55 deletions(-) create mode 100644 src/snowflake/snowpark/_internal/options.py diff --git a/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py b/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py index e3641da32c..001c2266a5 100644 --- a/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py +++ b/src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py @@ -13,7 +13,7 @@ from snowflake.connector import ProgrammingError from snowflake.connector.cursor import SnowflakeCursor -from snowflake.snowpark._internal.utils import pyarrow +from snowflake.snowpark._internal.options import pyarrow from snowflake.snowpark._internal.analyzer.binary_plan_node import ( AsOf, Except, diff --git a/src/snowflake/snowpark/_internal/data_source/datasource_reader.py b/src/snowflake/snowpark/_internal/data_source/datasource_reader.py index c8f568b508..a2a1c83b40 100644 --- a/src/snowflake/snowpark/_internal/data_source/datasource_reader.py +++ b/src/snowflake/snowpark/_internal/data_source/datasource_reader.py @@ -11,7 +11,7 @@ from snowflake.snowpark._internal.data_source.drivers.base_driver import BaseDriver from snowflake.snowpark.exceptions import SnowparkDataframeReaderException from snowflake.snowpark.types import StructType -from snowflake.snowpark._internal.utils import pandas as pd +from snowflake.snowpark._internal.options import pandas as pd import logging logger = logging.getLogger(__name__) diff --git a/src/snowflake/snowpark/_internal/data_source/drivers/base_driver.py b/src/snowflake/snowpark/_internal/data_source/drivers/base_driver.py index 3cf154764e..eafa31def5 100644 --- a/src/snowflake/snowpark/_internal/data_source/drivers/base_driver.py +++ b/src/snowflake/snowpark/_internal/data_source/drivers/base_driver.py @@ -4,7 +4,7 @@ from enum import Enum import datetime from typing import Dict, List, Callable, Any, Optional, TYPE_CHECKING -from snowflake.snowpark._internal.utils import pandas as pd +from snowflake.snowpark._internal.options import pandas as pd from snowflake.snowpark._internal.analyzer.analyzer_utils import unquote_if_quoted from snowflake.snowpark._internal.data_source.datasource_typing import ( diff --git a/src/snowflake/snowpark/_internal/event_table_telemetry.py b/src/snowflake/snowpark/_internal/event_table_telemetry.py index 3a5a494c6d..846d5c7d91 100644 --- a/src/snowflake/snowpark/_internal/event_table_telemetry.py +++ b/src/snowflake/snowpark/_internal/event_table_telemetry.py @@ -11,11 +11,11 @@ import snowflake.snowpark import requests -from snowflake.snowpark._internal.utils import ( +from snowflake.snowpark._internal.options import ( MissingOptionalDependency, ModuleLikeObject, - parse_table_name, ) +from snowflake.snowpark._internal.utils import parse_table_name _logger = getLogger(__name__) diff --git a/src/snowflake/snowpark/_internal/options.py b/src/snowflake/snowpark/_internal/options.py new file mode 100644 index 0000000000..9936dd4363 --- /dev/null +++ b/src/snowflake/snowpark/_internal/options.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved. +# + +from snowflake.connector.version import VERSION as connector_version + +IS_V5_DRIVER: bool = connector_version[0] >= 5 + +if IS_V5_DRIVER: + from snowflake.connector._common.extras import pandas # noqa: F401 + from snowflake.connector._common.extras import ModuleLikeObject # noqa: F401 + from snowflake.connector._common.extras import installed_pandas # noqa: F401 + from snowflake.connector._common.extras import ( + MissingOptionalDependency, + pyarrow, + installed_pyarrow, + ) +else: + from snowflake.connector.options import pandas # noqa: F401 + from snowflake.connector.options import ModuleLikeObject # noqa: F401 + from snowflake.connector.options import installed_pandas # noqa: F401 + from snowflake.connector.options import ( + MissingOptionalDependency, + MissingPandas, + pyarrow, + ) + + # connector.options (v4) never exported installed_pyarrow as its own name. + installed_pyarrow: bool = not isinstance(pyarrow, MissingOptionalDependency) + + +def _missing_pandas() -> MissingOptionalDependency: + # v4 has no __init__ override (no-arg-subclass only); v5 deleted + # MissingPandas in favor of the positional-arg form. + if IS_V5_DRIVER: + return MissingOptionalDependency("pandas") + return MissingPandas() diff --git a/src/snowflake/snowpark/_internal/server_connection.py b/src/snowflake/snowpark/_internal/server_connection.py index ed025ba2e8..add6656e0c 100644 --- a/src/snowflake/snowpark/_internal/server_connection.py +++ b/src/snowflake/snowpark/_internal/server_connection.py @@ -28,7 +28,7 @@ from snowflake.connector.constants import FIELD_ID_TO_NAME from snowflake.connector.cursor import ResultMetadata, SnowflakeCursor from snowflake.connector.errors import Error, NotSupportedError, ProgrammingError -from snowflake.snowpark._internal.utils import pandas +from snowflake.snowpark._internal.options import pandas from snowflake.snowpark._internal.analyzer.analyzer_utils import ( quote_name_without_upper_casing, ) diff --git a/src/snowflake/snowpark/_internal/type_utils.py b/src/snowflake/snowpark/_internal/type_utils.py index d790cae476..b27e50642a 100644 --- a/src/snowflake/snowpark/_internal/type_utils.py +++ b/src/snowflake/snowpark/_internal/type_utils.py @@ -36,7 +36,7 @@ import snowflake.snowpark.types # type: ignore from snowflake.connector.constants import FIELD_ID_TO_NAME from snowflake.connector.cursor import ResultMetadata -from snowflake.snowpark._internal.utils import installed_pandas, pandas +from snowflake.snowpark._internal.options import installed_pandas, pandas from snowflake.snowpark._internal.utils import quote_name from snowflake.snowpark.row import Row from snowflake.snowpark.types import ( diff --git a/src/snowflake/snowpark/_internal/udf_utils.py b/src/snowflake/snowpark/_internal/udf_utils.py index 7beb5e4369..de7d82a0d8 100644 --- a/src/snowflake/snowpark/_internal/udf_utils.py +++ b/src/snowflake/snowpark/_internal/udf_utils.py @@ -29,7 +29,7 @@ from packaging.requirements import Requirement import snowflake.snowpark -from snowflake.snowpark._internal.utils import installed_pandas, pandas +from snowflake.snowpark._internal.options import installed_pandas, pandas from snowflake.snowpark._internal import code_generation, type_utils from snowflake.snowpark._internal.analyzer.datatype_mapper import to_sql, to_sql_no_cast from snowflake.snowpark._internal.telemetry import TelemetryField diff --git a/src/snowflake/snowpark/_internal/utils.py b/src/snowflake/snowpark/_internal/utils.py index 6f75b52c63..74c390ad8c 100644 --- a/src/snowflake/snowpark/_internal/utils.py +++ b/src/snowflake/snowpark/_internal/utils.py @@ -61,35 +61,11 @@ IS_V5_DRIVER: bool = connector_version[0] >= 5 -if IS_V5_DRIVER: - from snowflake.connector._common.extras import pandas # noqa: F401 - from snowflake.connector._common.extras import ( - MissingOptionalDependency, - ModuleLikeObject, - pyarrow, - installed_pandas, - installed_pyarrow, - ) -else: - from snowflake.connector.options import pandas # noqa: F401 - from snowflake.connector.options import ( - MissingOptionalDependency, - MissingPandas, - ModuleLikeObject, - pyarrow, - installed_pandas, - ) - - # connector.options (v4) never exported installed_pyarrow as its own name. - installed_pyarrow: bool = not isinstance(pyarrow, MissingOptionalDependency) - - -def _missing_pandas() -> MissingOptionalDependency: - # v4 has no __init__ override (no-arg-subclass only); v5 deleted - # MissingPandas in favor of the positional-arg form. - if IS_V5_DRIVER: - return MissingOptionalDependency("pandas") - return MissingPandas() +from snowflake.snowpark._internal.options import ( # noqa: E402 + MissingOptionalDependency, + ModuleLikeObject, + installed_pandas, +) if TYPE_CHECKING: diff --git a/src/snowflake/snowpark/async_job.py b/src/snowflake/snowpark/async_job.py index b18732150b..d4919eed10 100644 --- a/src/snowflake/snowpark/async_job.py +++ b/src/snowflake/snowpark/async_job.py @@ -8,7 +8,7 @@ import snowflake.snowpark from snowflake.connector.errors import DatabaseError -from snowflake.snowpark._internal.utils import pandas +from snowflake.snowpark._internal.options import pandas from snowflake.snowpark._internal.analyzer.analyzer_utils import result_scan_statement from snowflake.snowpark._internal.analyzer.snowflake_plan import Query from snowflake.snowpark._internal.utils import ( diff --git a/src/snowflake/snowpark/dataframe.py b/src/snowflake/snowpark/dataframe.py index 76e77169eb..dd69e90d65 100644 --- a/src/snowflake/snowpark/dataframe.py +++ b/src/snowflake/snowpark/dataframe.py @@ -31,7 +31,7 @@ import snowflake.snowpark import snowflake.snowpark.context as context import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto -from snowflake.snowpark._internal.utils import installed_pandas, pandas, pyarrow +from snowflake.snowpark._internal.options import installed_pandas, pandas, pyarrow from snowflake.snowpark._internal.analyzer.binary_plan_node import ( AsOf, diff --git a/src/snowflake/snowpark/mock/_options.py b/src/snowflake/snowpark/mock/_options.py index 37d71e3d4f..5ac0d3d807 100644 --- a/src/snowflake/snowpark/mock/_options.py +++ b/src/snowflake/snowpark/mock/_options.py @@ -4,11 +4,11 @@ import importlib -from snowflake.snowpark._internal.utils import ( - IS_V5_DRIVER, +from snowflake.snowpark._internal.options import ( MissingOptionalDependency, _missing_pandas, ) +from snowflake.snowpark._internal.utils import IS_V5_DRIVER try: import pandas diff --git a/src/snowflake/snowpark/relational_grouped_dataframe.py b/src/snowflake/snowpark/relational_grouped_dataframe.py index 8b427ebbd1..a0fc656b94 100644 --- a/src/snowflake/snowpark/relational_grouped_dataframe.py +++ b/src/snowflake/snowpark/relational_grouped_dataframe.py @@ -8,7 +8,7 @@ from snowflake.snowpark._internal.error_message import SnowparkClientExceptionMessages import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto import snowflake.snowpark.context as context -from snowflake.snowpark._internal.utils import pandas +from snowflake.snowpark._internal.options import pandas from snowflake.snowpark._internal.analyzer.analyzer_utils import unquote_if_quoted from snowflake.snowpark import functions from snowflake.snowpark._internal.analyzer.expression import ( diff --git a/src/snowflake/snowpark/session.py b/src/snowflake/snowpark/session.py index 7484504054..a461116324 100644 --- a/src/snowflake/snowpark/session.py +++ b/src/snowflake/snowpark/session.py @@ -42,7 +42,7 @@ import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto import snowflake.snowpark.context as context from snowflake.connector import ProgrammingError, SnowflakeConnection -from snowflake.snowpark._internal.utils import installed_pandas, pandas, pyarrow +from snowflake.snowpark._internal.options import installed_pandas, pandas, pyarrow from snowflake.connector.pandas_tools import write_pandas from snowflake.snowpark import UDFProfiler diff --git a/src/snowflake/snowpark/types.py b/src/snowflake/snowpark/types.py index 3d97816615..a3f8e10241 100644 --- a/src/snowflake/snowpark/types.py +++ b/src/snowflake/snowpark/types.py @@ -16,11 +16,8 @@ import snowflake.snowpark._internal.proto.generated.ast_pb2 as proto # Use correct version from here: -from snowflake.snowpark._internal.utils import ( - installed_pandas, - pandas, - quote_name, -) +from snowflake.snowpark._internal.options import installed_pandas, pandas +from snowflake.snowpark._internal.utils import quote_name # TODO: connector installed_pandas is broken. If pyarrow is not installed, but pandas is this function returns the wrong answer. # The core issue is that in the connector detection of both pandas/arrow are mixed, which is wrong. diff --git a/tests/integ/scala/test_datatype_suite.py b/tests/integ/scala/test_datatype_suite.py index 0bed68f20e..6b1bbe7cc0 100644 --- a/tests/integ/scala/test_datatype_suite.py +++ b/tests/integ/scala/test_datatype_suite.py @@ -12,7 +12,7 @@ from unittest import mock import snowflake.snowpark.context as context -from snowflake.snowpark._internal.utils import installed_pandas +from snowflake.snowpark._internal.options import installed_pandas from snowflake.snowpark import Row from snowflake.snowpark.dataframe import DataFrame from snowflake.snowpark.exceptions import SnowparkSQLException diff --git a/tests/integ/scala/test_update_delete_merge_suite.py b/tests/integ/scala/test_update_delete_merge_suite.py index 1c45d701d3..534c0824b0 100644 --- a/tests/integ/scala/test_update_delete_merge_suite.py +++ b/tests/integ/scala/test_update_delete_merge_suite.py @@ -8,7 +8,7 @@ import pytest -from snowflake.snowpark._internal.utils import installed_pandas +from snowflake.snowpark._internal.options import installed_pandas from snowflake.snowpark import ( DeleteResult, MergeResult, diff --git a/tests/integ/test_cte.py b/tests/integ/test_cte.py index 966a50bd8e..fab97afc67 100644 --- a/tests/integ/test_cte.py +++ b/tests/integ/test_cte.py @@ -11,7 +11,7 @@ import pytest from snowflake.connector.errors import ProgrammingError -from snowflake.snowpark._internal.utils import installed_pandas +from snowflake.snowpark._internal.options import installed_pandas from snowflake.snowpark import Window from snowflake.snowpark._internal.analyzer import analyzer from snowflake.snowpark._internal.analyzer.snowflake_plan import PlanQueryType, Query diff --git a/tests/integ/test_function.py b/tests/integ/test_function.py index e32dfa636b..81cc445293 100644 --- a/tests/integ/test_function.py +++ b/tests/integ/test_function.py @@ -165,7 +165,7 @@ year, ) import functools -from snowflake.snowpark._internal.utils import installed_pandas +from snowflake.snowpark._internal.options import installed_pandas from snowflake.snowpark.functions import udf, vectorized from snowflake.snowpark.udf import UserDefinedFunction from snowflake.snowpark.types import ( diff --git a/tests/unit/test_internal_utils.py b/tests/unit/test_internal_utils.py index 97843ab42d..6d41a8a40e 100644 --- a/tests/unit/test_internal_utils.py +++ b/tests/unit/test_internal_utils.py @@ -5,7 +5,7 @@ import random import pytest -from snowflake.snowpark._internal import utils +from snowflake.snowpark._internal import options, utils from snowflake.snowpark._internal.utils import ( generate_random_alphanumeric, split_snowflake_identifier_with_dot, @@ -201,6 +201,6 @@ def test_split_snowflake_identifier_with_dot(string, expected_result): def test_missing_pandas(): - result = utils._missing_pandas() - assert isinstance(result, utils.MissingOptionalDependency) + result = options._missing_pandas() + assert isinstance(result, options.MissingOptionalDependency) assert result._dep_name == "pandas"