diff --git a/DESCRIPTION.md b/DESCRIPTION.md index a305a53dc..7beceea1f 100644 --- a/DESCRIPTION.md +++ b/DESCRIPTION.md @@ -13,6 +13,7 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne - 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. - Added support for new authentication mechanism PAT with external session ID. - Added `client_fetch_use_mp` parameter that enables multiprocessed fetching of result batches. + - Make botocore and boto3 an optional dependencies pack. - v3.15.1(May 20, 2025) - Added basic arrow support for Interval types. diff --git a/setup.cfg b/setup.cfg index 967cea737..52fcde5f5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,8 +44,6 @@ python_requires = >=3.9 packages = find_namespace: install_requires = asn1crypto>0.24.0,<2.0.0 - boto3>=1.24 - botocore>=1.24 cffi>=1.9,<2.0.0 cryptography>=3.1.0,<=44.0.3 pyOpenSSL>=22.0.0,<26.0.0 @@ -97,3 +95,6 @@ pandas = pyarrow<19.0.0 secure-local-storage = keyring>=23.1.0,<26.0.0 +aws = + boto3>=1.24 + botocore>=1.24 diff --git a/src/snowflake/connector/options.py b/src/snowflake/connector/options.py index 8454ab169..7d1dd7f30 100644 --- a/src/snowflake/connector/options.py +++ b/src/snowflake/connector/options.py @@ -48,6 +48,18 @@ class MissingKeyring(MissingOptionalDependency): _dep_name = "keyring" +class MissingBoto3(MissingOptionalDependency): + """The class is specifically for boto3 optional dependency.""" + + _dep_name = "boto3" + + +class MissingBotoCore(MissingOptionalDependency): + """The class is specifically for botocore optional dependency.""" + + _dep_name = "botocore" + + ModuleLikeObject = Union[ModuleType, MissingOptionalDependency] @@ -126,6 +138,32 @@ def _import_or_missing_keyring_option() -> tuple[ModuleLikeObject, bool]: return MissingKeyring(), False +def _import_or_missing_botocore_option() -> tuple[ModuleLikeObject, bool]: + """This function tries importing the following packages: botocore. + + If available it returns botocore package with a flag of whether it was imported. + """ + try: + botocore = importlib.import_module("botocore") + return botocore, True + except ImportError: + return MissingBotoCore(), False + + +def _import_or_missing_boto3_option() -> tuple[ModuleLikeObject, bool]: + """This function tries importing the following packages: boto3. + + If available it returns boto3 package with a flag of whether it was imported. + """ + try: + boto3 = importlib.import_module("boto3") + return boto3, True + except ImportError: + return MissingBoto3(), False + + # 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() +boto3, installed_boto3 = _import_or_missing_boto3_option() +botocore, installed_botocore = _import_or_missing_botocore_option() diff --git a/src/snowflake/connector/wif_util.py b/src/snowflake/connector/wif_util.py index 3449cdd5e..9d450d211 100644 --- a/src/snowflake/connector/wif_util.py +++ b/src/snowflake/connector/wif_util.py @@ -7,11 +7,14 @@ from dataclasses import dataclass from enum import Enum, unique -import boto3 import jwt -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest -from botocore.utils import InstanceMetadataRegionFetcher + +from .options import boto3, installed_boto3, installed_botocore + +if installed_botocore: + from botocore.auth import SigV4Auth + from botocore.awsrequest import AWSRequest + from botocore.utils import InstanceMetadataRegionFetcher from .errorcode import ER_WIF_CREDENTIALS_NOT_FOUND from .errors import ProgrammingError @@ -190,6 +193,15 @@ def create_aws_attestation() -> WorkloadIdentityAttestation | None: If the application isn't running on AWS or no credentials were found, returns None. """ + if not (installed_boto3 and installed_botocore): + logger.error("AWS attestation requires botocore and boto3 libraries") + _warn( + "Dependencies botocore and boto3 are not installed, cannot create AWS Workload Identity attestation." + "Please install these modules using the following command:\n" + " pip install snowflake-connector-python[aws]" + ) + return None + aws_creds = boto3.session.Session().get_credentials() if not aws_creds: logger.debug("No AWS credentials were found.") diff --git a/test/csp_helpers.py b/test/csp_helpers.py index ac3533616..336ec6131 100644 --- a/test/csp_helpers.py +++ b/test/csp_helpers.py @@ -9,8 +9,12 @@ from urllib.parse import parse_qs, urlparse import jwt -from botocore.awsrequest import AWSRequest -from botocore.credentials import Credentials + +from snowflake.connector.options import installed_botocore + +if installed_botocore: + from botocore.awsrequest import AWSRequest + from botocore.credentials import Credentials from snowflake.connector.vendored.requests.exceptions import ConnectTimeout, HTTPError from snowflake.connector.vendored.requests.models import Response diff --git a/tox.ini b/tox.ini index 23df98561..e32e27932 100644 --- a/tox.ini +++ b/tox.ini @@ -28,6 +28,7 @@ extras = development pandas: pandas sso: secure-local-storage + aws package = wheel setenv = COVERAGE_FILE = {env:COVERAGE_FILE:{toxworkdir}/.coverage.{envname}}