diff --git a/DESCRIPTION.md b/DESCRIPTION.md index fd5f9713d..b08755105 100644 --- a/DESCRIPTION.md +++ b/DESCRIPTION.md @@ -11,6 +11,7 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne - Bumped numpy dependency from <2.1.0 to <=2.2.4 - Added Windows support for Python 3.13. - Add `bulk_upload_chunks` parameter to `write_pandas` function. Setting this parameter to True changes the behaviour of write_pandas function to first write all the data chunks to the local disk and then perform the wildcard upload of the chunks folder to the stage. In default behaviour the chunks are being saved, uploaded and deleted one by one. + - Implemented lazy import for pandas to improve loading performance. - v3.15.1(May 20, 2025) diff --git a/src/snowflake/connector/options.py b/src/snowflake/connector/options.py index 8454ab169..2701d636d 100644 --- a/src/snowflake/connector/options.py +++ b/src/snowflake/connector/options.py @@ -10,8 +10,6 @@ from packaging.requirements import Requirement -from . import errors - logger = getLogger(__name__) """This module helps to manage optional dependencies. @@ -33,6 +31,8 @@ class MissingOptionalDependency: _dep_name = "not set" def __getattr__(self, item): + from . import errors + raise errors.MissingDependencyError(self._dep_name) @@ -114,6 +114,15 @@ def _import_or_missing_pandas_option() -> ( return MissingPandas(), MissingPandas(), False +def installed_pandas() -> bool: + """This function checks if pandas is available and compatible.""" + try: + importlib.import_module("pandas") + return True + except ImportError: + return False + + def _import_or_missing_keyring_option() -> tuple[ModuleLikeObject, bool]: """This function tries importing the following packages: keyring. @@ -127,5 +136,25 @@ def _import_or_missing_keyring_option() -> tuple[ModuleLikeObject, bool]: # Create actual constants to be imported from this file -pandas, pyarrow, installed_pandas = _import_or_missing_pandas_option() keyring, installed_keyring = _import_or_missing_keyring_option() + + +def __getattr__(name): + if name == "pandas": + try: + return importlib.import_module("pandas") + except ImportError: + return MissingPandas() + + elif name == "pyarrow": + try: + return importlib.import_module("pyarrow") + except ImportError: + from . import errors + + raise errors.MissingDependencyError("pyarrow") + + elif name == "installed_pandas": + return installed_pandas() + + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/src/snowflake/connector/result_set.py b/src/snowflake/connector/result_set.py index b633b41a0..81d6fcb13 100644 --- a/src/snowflake/connector/result_set.py +++ b/src/snowflake/connector/result_set.py @@ -18,7 +18,6 @@ from .constants import IterUnit from .errors import NotSupportedError -from .options import pandas from .options import pyarrow as pa from .result_batch import ( ArrowResultBatch, @@ -215,6 +214,8 @@ def _fetch_pandas_batches(self, **kwargs) -> Iterator[DataFrame]: def _fetch_pandas_all(self, **kwargs) -> DataFrame: """Fetches a single Pandas dataframe.""" + from .options import pandas + concat_args = list(inspect.signature(pandas.concat).parameters) concat_kwargs = {k: kwargs.pop(k) for k in dict(kwargs) if k in concat_args} dataframes = list(self._fetch_pandas_batches(is_fetch_all=True, **kwargs))