snowflake-connector-python 4.4.0__cp314-cp314-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/connector/__init__.py +118 -0
- snowflake/connector/_query_context_cache.py +276 -0
- snowflake/connector/_sql_util.py +48 -0
- snowflake/connector/_utils.py +353 -0
- snowflake/connector/aio/README.md +88 -0
- snowflake/connector/aio/__init__.py +163 -0
- snowflake/connector/aio/_azure_storage_client.py +227 -0
- snowflake/connector/aio/_bind_upload_agent.py +69 -0
- snowflake/connector/aio/_connection.py +1216 -0
- snowflake/connector/aio/_cursor.py +1402 -0
- snowflake/connector/aio/_description.py +5 -0
- snowflake/connector/aio/_direct_file_operation_utils.py +88 -0
- snowflake/connector/aio/_file_transfer_agent.py +316 -0
- snowflake/connector/aio/_gcs_storage_client.py +360 -0
- snowflake/connector/aio/_network.py +860 -0
- snowflake/connector/aio/_ocsp_asn1crypto.py +51 -0
- snowflake/connector/aio/_ocsp_snowflake.py +608 -0
- snowflake/connector/aio/_pandas_tools.py +561 -0
- snowflake/connector/aio/_result_batch.py +442 -0
- snowflake/connector/aio/_result_set.py +309 -0
- snowflake/connector/aio/_s3_storage_client.py +460 -0
- snowflake/connector/aio/_session_manager.py +588 -0
- snowflake/connector/aio/_storage_client.py +331 -0
- snowflake/connector/aio/_telemetry.py +97 -0
- snowflake/connector/aio/_time_util.py +57 -0
- snowflake/connector/aio/_wif_util.py +324 -0
- snowflake/connector/aio/auth/__init__.py +52 -0
- snowflake/connector/aio/auth/_auth.py +406 -0
- snowflake/connector/aio/auth/_by_plugin.py +131 -0
- snowflake/connector/aio/auth/_default.py +28 -0
- snowflake/connector/aio/auth/_idtoken.py +54 -0
- snowflake/connector/aio/auth/_keypair.py +60 -0
- snowflake/connector/aio/auth/_no_auth.py +31 -0
- snowflake/connector/aio/auth/_oauth.py +27 -0
- snowflake/connector/aio/auth/_oauth_code.py +117 -0
- snowflake/connector/aio/auth/_oauth_credentials.py +100 -0
- snowflake/connector/aio/auth/_okta.py +247 -0
- snowflake/connector/aio/auth/_pat.py +27 -0
- snowflake/connector/aio/auth/_usrpwdmfa.py +30 -0
- snowflake/connector/aio/auth/_webbrowser.py +405 -0
- snowflake/connector/aio/auth/_workload_identity.py +60 -0
- snowflake/connector/arrow_context.py +211 -0
- snowflake/connector/auth/__init__.py +53 -0
- snowflake/connector/auth/_auth.py +669 -0
- snowflake/connector/auth/_http_server.py +257 -0
- snowflake/connector/auth/_oauth_base.py +472 -0
- snowflake/connector/auth/by_plugin.py +219 -0
- snowflake/connector/auth/default.py +36 -0
- snowflake/connector/auth/idtoken.py +72 -0
- snowflake/connector/auth/keypair.py +225 -0
- snowflake/connector/auth/no_auth.py +39 -0
- snowflake/connector/auth/oauth.py +49 -0
- snowflake/connector/auth/oauth_code.py +490 -0
- snowflake/connector/auth/oauth_credentials.py +71 -0
- snowflake/connector/auth/okta.py +339 -0
- snowflake/connector/auth/pat.py +40 -0
- snowflake/connector/auth/usrpwdmfa.py +69 -0
- snowflake/connector/auth/webbrowser.py +514 -0
- snowflake/connector/auth/workload_identity.py +110 -0
- snowflake/connector/azure_storage_client.py +278 -0
- snowflake/connector/backoff_policies.py +141 -0
- snowflake/connector/bind_upload_agent.py +89 -0
- snowflake/connector/cache.py +696 -0
- snowflake/connector/compat.py +127 -0
- snowflake/connector/config_manager.py +553 -0
- snowflake/connector/connection.py +2671 -0
- snowflake/connector/connection_diagnostic.py +793 -0
- snowflake/connector/constants.py +460 -0
- snowflake/connector/converter.py +809 -0
- snowflake/connector/converter_issue23517.py +87 -0
- snowflake/connector/converter_null.py +14 -0
- snowflake/connector/converter_snowsql.py +205 -0
- snowflake/connector/crl.py +919 -0
- snowflake/connector/crl_cache.py +734 -0
- snowflake/connector/cursor.py +2099 -0
- snowflake/connector/dbapi.py +53 -0
- snowflake/connector/description.py +21 -0
- snowflake/connector/direct_file_operation_utils.py +88 -0
- snowflake/connector/encryption_util.py +220 -0
- snowflake/connector/errorcode.py +94 -0
- snowflake/connector/errors.py +666 -0
- snowflake/connector/externals_utils/__init__.py +0 -0
- snowflake/connector/externals_utils/externals_setup.py +30 -0
- snowflake/connector/feature.py +4 -0
- snowflake/connector/file_compression_type.py +118 -0
- snowflake/connector/file_lock.py +72 -0
- snowflake/connector/file_transfer_agent.py +1244 -0
- snowflake/connector/file_util.py +153 -0
- snowflake/connector/gcs_storage_client.py +476 -0
- snowflake/connector/gzip_decoder.py +85 -0
- snowflake/connector/interval_util.py +26 -0
- snowflake/connector/local_storage_client.py +90 -0
- snowflake/connector/log_configuration.py +62 -0
- snowflake/connector/logging_utils/__init__.py +0 -0
- snowflake/connector/logging_utils/filters.py +72 -0
- snowflake/connector/minicore/__init__.py +0 -0
- snowflake/connector/minicore/aix_ppc64/libsf_mini_core.a +0 -0
- snowflake/connector/minicore/aix_ppc64/libsf_mini_core_static.a +0 -0
- snowflake/connector/minicore/linux_aarch64_glibc/libsf_mini_core.so +0 -0
- snowflake/connector/minicore/linux_aarch64_musl/libsf_mini_core.so +0 -0
- snowflake/connector/minicore/linux_x86_64_glibc/libsf_mini_core.so +0 -0
- snowflake/connector/minicore/linux_x86_64_musl/libsf_mini_core.so +0 -0
- snowflake/connector/minicore/macos_aarch64/libsf_mini_core.dylib +0 -0
- snowflake/connector/minicore/macos_x86_64/libsf_mini_core.dylib +0 -0
- snowflake/connector/minicore/windows_x86_64/sf_mini_core.dll +0 -0
- snowflake/connector/minicore/windows_x86_64/sf_mini_core_static.lib +0 -0
- snowflake/connector/nanoarrow_arrow_iterator.cp314-win_amd64.pyd +0 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.cpp +60 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.cpp +21 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.hpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.cpp +570 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.hpp +98 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.cpp +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.hpp +115 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.cpp +1140 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.hpp +138 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.cpp +47 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.hpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.cpp +83 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.cpp +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.hpp +72 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.cpp +73 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.hpp +28 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.cpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IColumnConverter.hpp +17 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.cpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.hpp +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.cpp +75 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.hpp +57 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/LICENSE.txt +209 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.cpp +75 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.hpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.cpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.cpp +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.hpp +95 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.cpp +57 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.hpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.cpp +34 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.hpp +48 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.cpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.hpp +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.cpp +346 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.hpp +145 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/macros.hpp +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.cpp +67 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.hpp +68 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_accessors.h +101 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_alloc.h +127 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_assert.h +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_builder.h +1908 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_emitter.h +215 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_endian.h +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_epilogue.h +7 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_flatbuffers.h +55 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_identifier.h +148 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_iov.h +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_prologue.h +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_refmap.h +144 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_rtconfig.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_types.h +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_verifier.h +239 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/flatcc_portable.h +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/paligned_alloc.h +210 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pattributes.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_pop.h +20 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_push.h +51 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian.h +206 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian_detect.h +118 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinline.h +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinttypes.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable.h +2 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable_basic.h +25 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstatic_assert.h +67 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdalign.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdint.h +898 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/punaligned.h +190 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pversion.h +6 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pwarnings.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc.c +3204 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.c +3217 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.h +3618 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.hpp +379 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_arrow_iterator.pyx +262 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.c +512 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.h +350 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.c +33273 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.h +397 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.cpp +100 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.hpp +49 -0
- snowflake/connector/network.py +1227 -0
- snowflake/connector/ocsp_asn1crypto.py +447 -0
- snowflake/connector/ocsp_snowflake.py +1939 -0
- snowflake/connector/options.py +179 -0
- snowflake/connector/os_details.py +88 -0
- snowflake/connector/pandas_tools.py +734 -0
- snowflake/connector/platform_detection.py +555 -0
- snowflake/connector/proxy.py +28 -0
- snowflake/connector/py.typed +0 -0
- snowflake/connector/result_batch.py +917 -0
- snowflake/connector/result_set.py +340 -0
- snowflake/connector/s3_storage_client.py +614 -0
- snowflake/connector/secret_detector.py +181 -0
- snowflake/connector/session_manager.py +650 -0
- snowflake/connector/sf_dirs.py +64 -0
- snowflake/connector/sfbinaryformat.py +35 -0
- snowflake/connector/sfdatetime.py +345 -0
- snowflake/connector/snow_logging.py +144 -0
- snowflake/connector/sqlstate.py +9 -0
- snowflake/connector/ssd_internal_keys.py +30 -0
- snowflake/connector/ssl_wrap_socket.py +281 -0
- snowflake/connector/storage_client.py +486 -0
- snowflake/connector/telemetry.py +254 -0
- snowflake/connector/telemetry_oob.py +544 -0
- snowflake/connector/test_util.py +30 -0
- snowflake/connector/time_util.py +159 -0
- snowflake/connector/token_cache.py +457 -0
- snowflake/connector/tool/__init__.py +0 -0
- snowflake/connector/tool/dump_certs.py +57 -0
- snowflake/connector/tool/dump_ocsp_response.py +139 -0
- snowflake/connector/tool/dump_ocsp_response_cache.py +194 -0
- snowflake/connector/tool/probe_connection.py +69 -0
- snowflake/connector/url_util.py +55 -0
- snowflake/connector/util_text.py +320 -0
- snowflake/connector/vendored/__init__.py +3 -0
- snowflake/connector/vendored/requests/LICENSE +175 -0
- snowflake/connector/vendored/requests/__init__.py +184 -0
- snowflake/connector/vendored/requests/__version__.py +14 -0
- snowflake/connector/vendored/requests/_internal_utils.py +50 -0
- snowflake/connector/vendored/requests/adapters.py +696 -0
- snowflake/connector/vendored/requests/api.py +157 -0
- snowflake/connector/vendored/requests/auth.py +314 -0
- snowflake/connector/vendored/requests/certs.py +17 -0
- snowflake/connector/vendored/requests/compat.py +106 -0
- snowflake/connector/vendored/requests/cookies.py +561 -0
- snowflake/connector/vendored/requests/exceptions.py +151 -0
- snowflake/connector/vendored/requests/help.py +134 -0
- snowflake/connector/vendored/requests/hooks.py +33 -0
- snowflake/connector/vendored/requests/models.py +1039 -0
- snowflake/connector/vendored/requests/packages.py +23 -0
- snowflake/connector/vendored/requests/sessions.py +831 -0
- snowflake/connector/vendored/requests/status_codes.py +128 -0
- snowflake/connector/vendored/requests/structures.py +99 -0
- snowflake/connector/vendored/requests/utils.py +1086 -0
- snowflake/connector/vendored/urllib3/LICENSE.txt +21 -0
- snowflake/connector/vendored/urllib3/__init__.py +211 -0
- snowflake/connector/vendored/urllib3/_base_connection.py +165 -0
- snowflake/connector/vendored/urllib3/_collections.py +487 -0
- snowflake/connector/vendored/urllib3/_request_methods.py +278 -0
- snowflake/connector/vendored/urllib3/_version.py +34 -0
- snowflake/connector/vendored/urllib3/connection.py +1108 -0
- snowflake/connector/vendored/urllib3/connectionpool.py +1178 -0
- snowflake/connector/vendored/urllib3/contrib/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/__init__.py +17 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/connection.py +260 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/emscripten_fetch_worker.js +110 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/fetch.py +726 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/request.py +22 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/response.py +277 -0
- snowflake/connector/vendored/urllib3/contrib/pyopenssl.py +564 -0
- snowflake/connector/vendored/urllib3/contrib/socks.py +228 -0
- snowflake/connector/vendored/urllib3/exceptions.py +335 -0
- snowflake/connector/vendored/urllib3/fields.py +341 -0
- snowflake/connector/vendored/urllib3/filepost.py +89 -0
- snowflake/connector/vendored/urllib3/http2/__init__.py +53 -0
- snowflake/connector/vendored/urllib3/http2/connection.py +356 -0
- snowflake/connector/vendored/urllib3/http2/probe.py +87 -0
- snowflake/connector/vendored/urllib3/poolmanager.py +651 -0
- snowflake/connector/vendored/urllib3/py.typed +2 -0
- snowflake/connector/vendored/urllib3/response.py +1480 -0
- snowflake/connector/vendored/urllib3/util/__init__.py +42 -0
- snowflake/connector/vendored/urllib3/util/connection.py +137 -0
- snowflake/connector/vendored/urllib3/util/proxy.py +43 -0
- snowflake/connector/vendored/urllib3/util/request.py +263 -0
- snowflake/connector/vendored/urllib3/util/response.py +101 -0
- snowflake/connector/vendored/urllib3/util/retry.py +549 -0
- snowflake/connector/vendored/urllib3/util/ssl_.py +527 -0
- snowflake/connector/vendored/urllib3/util/ssl_match_hostname.py +159 -0
- snowflake/connector/vendored/urllib3/util/ssltransport.py +271 -0
- snowflake/connector/vendored/urllib3/util/timeout.py +275 -0
- snowflake/connector/vendored/urllib3/util/url.py +469 -0
- snowflake/connector/vendored/urllib3/util/util.py +42 -0
- snowflake/connector/vendored/urllib3/util/wait.py +124 -0
- snowflake/connector/version.py +3 -0
- snowflake/connector/wif_util.py +434 -0
- snowflake_connector_python-4.4.0.dist-info/DELVEWHEEL +2 -0
- snowflake_connector_python-4.4.0.dist-info/METADATA +1591 -0
- snowflake_connector_python-4.4.0.dist-info/RECORD +302 -0
- snowflake_connector_python-4.4.0.dist-info/WHEEL +5 -0
- snowflake_connector_python-4.4.0.dist-info/entry_points.txt +4 -0
- snowflake_connector_python-4.4.0.dist-info/licenses/LICENSE.txt +202 -0
- snowflake_connector_python-4.4.0.dist-info/licenses/NOTICE +8 -0
- snowflake_connector_python-4.4.0.dist-info/top_level.txt +1 -0
- snowflake_connector_python.libs/msvcp140-0f885b509a685d2bbfa652fed26b5fb3.dll +0 -0
|
@@ -0,0 +1,1216 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import atexit
|
|
5
|
+
import copy
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import pathlib
|
|
9
|
+
import sys
|
|
10
|
+
import typing
|
|
11
|
+
import uuid
|
|
12
|
+
import warnings
|
|
13
|
+
from contextlib import suppress
|
|
14
|
+
from io import StringIO
|
|
15
|
+
from logging import getLogger
|
|
16
|
+
from types import TracebackType
|
|
17
|
+
from typing import Any, AsyncIterator, Iterable, TypeVar
|
|
18
|
+
|
|
19
|
+
from snowflake.connector import (
|
|
20
|
+
DatabaseError,
|
|
21
|
+
EasyLoggingConfigPython,
|
|
22
|
+
Error,
|
|
23
|
+
OperationalError,
|
|
24
|
+
ProgrammingError,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from .._query_context_cache import QueryContextCache
|
|
28
|
+
from ..compat import IS_LINUX, quote, urlencode
|
|
29
|
+
from ..config_manager import CONFIG_MANAGER, _get_default_connection_params
|
|
30
|
+
from ..connection import DEFAULT_CONFIGURATION as DEFAULT_CONFIGURATION_SYNC
|
|
31
|
+
from ..connection import SnowflakeConnection as SnowflakeConnectionSync
|
|
32
|
+
from ..connection import _get_private_bytes_from_file
|
|
33
|
+
from ..constants import (
|
|
34
|
+
_CONNECTIVITY_ERR_MSG,
|
|
35
|
+
_OAUTH_DEFAULT_SCOPE,
|
|
36
|
+
PARAMETER_AUTOCOMMIT,
|
|
37
|
+
PARAMETER_CLIENT_PREFETCH_THREADS,
|
|
38
|
+
PARAMETER_CLIENT_REQUEST_MFA_TOKEN,
|
|
39
|
+
PARAMETER_CLIENT_SESSION_KEEP_ALIVE,
|
|
40
|
+
PARAMETER_CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY,
|
|
41
|
+
PARAMETER_CLIENT_STORE_TEMPORARY_CREDENTIAL,
|
|
42
|
+
PARAMETER_CLIENT_TELEMETRY_ENABLED,
|
|
43
|
+
PARAMETER_CLIENT_VALIDATE_DEFAULT_PARAMETERS,
|
|
44
|
+
PARAMETER_ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1,
|
|
45
|
+
PARAMETER_QUERY_CONTEXT_CACHE_SIZE,
|
|
46
|
+
PARAMETER_SERVICE_NAME,
|
|
47
|
+
PARAMETER_TIMEZONE,
|
|
48
|
+
QueryStatus,
|
|
49
|
+
)
|
|
50
|
+
from ..description import PLATFORM, PYTHON_VERSION, SNOWFLAKE_CONNECTOR_VERSION
|
|
51
|
+
from ..errorcode import (
|
|
52
|
+
ER_CONNECTION_IS_CLOSED,
|
|
53
|
+
ER_FAILED_TO_CONNECT_TO_DB,
|
|
54
|
+
ER_INVALID_VALUE,
|
|
55
|
+
ER_INVALID_WIF_SETTINGS,
|
|
56
|
+
)
|
|
57
|
+
from ..network import (
|
|
58
|
+
DEFAULT_AUTHENTICATOR,
|
|
59
|
+
EXTERNAL_BROWSER_AUTHENTICATOR,
|
|
60
|
+
KEY_PAIR_AUTHENTICATOR,
|
|
61
|
+
OAUTH_AUTHENTICATOR,
|
|
62
|
+
OAUTH_AUTHORIZATION_CODE,
|
|
63
|
+
OAUTH_CLIENT_CREDENTIALS,
|
|
64
|
+
PAT_WITH_EXTERNAL_SESSION,
|
|
65
|
+
PROGRAMMATIC_ACCESS_TOKEN,
|
|
66
|
+
REQUEST_ID,
|
|
67
|
+
USR_PWD_MFA_AUTHENTICATOR,
|
|
68
|
+
WORKLOAD_IDENTITY_AUTHENTICATOR,
|
|
69
|
+
ReauthenticationRequest,
|
|
70
|
+
)
|
|
71
|
+
from ..sqlstate import SQLSTATE_CONNECTION_NOT_EXISTS, SQLSTATE_FEATURE_NOT_SUPPORTED
|
|
72
|
+
from ..telemetry import TelemetryData, TelemetryField
|
|
73
|
+
from ..time_util import get_time_millis
|
|
74
|
+
from ..util_text import split_statements
|
|
75
|
+
from ..wif_util import AttestationProvider
|
|
76
|
+
from ._cursor import SnowflakeCursor, SnowflakeCursorBase
|
|
77
|
+
from ._description import CLIENT_NAME
|
|
78
|
+
from ._direct_file_operation_utils import FileOperationParser, StreamDownloader
|
|
79
|
+
from ._network import SnowflakeRestful
|
|
80
|
+
from ._session_manager import (
|
|
81
|
+
AioHttpConfig,
|
|
82
|
+
SessionManager,
|
|
83
|
+
SessionManagerFactory,
|
|
84
|
+
SnowflakeSSLConnectorFactory,
|
|
85
|
+
)
|
|
86
|
+
from ._telemetry import TelemetryClient
|
|
87
|
+
from ._time_util import HeartBeatTimer
|
|
88
|
+
from .auth import (
|
|
89
|
+
FIRST_PARTY_AUTHENTICATORS,
|
|
90
|
+
Auth,
|
|
91
|
+
AuthByDefault,
|
|
92
|
+
AuthByIdToken,
|
|
93
|
+
AuthByKeyPair,
|
|
94
|
+
AuthByOAuth,
|
|
95
|
+
AuthByOauthCode,
|
|
96
|
+
AuthByOauthCredentials,
|
|
97
|
+
AuthByOkta,
|
|
98
|
+
AuthByPAT,
|
|
99
|
+
AuthByPlugin,
|
|
100
|
+
AuthByUsrPwdMfa,
|
|
101
|
+
AuthByWebBrowser,
|
|
102
|
+
AuthByWorkloadIdentity,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
logger = getLogger(__name__)
|
|
106
|
+
|
|
107
|
+
# deep copy to avoid pollute sync config
|
|
108
|
+
DEFAULT_CONFIGURATION = copy.deepcopy(DEFAULT_CONFIGURATION_SYNC)
|
|
109
|
+
DEFAULT_CONFIGURATION["application"] = (CLIENT_NAME, (type(None), str))
|
|
110
|
+
|
|
111
|
+
if sys.version_info >= (3, 13) or typing.TYPE_CHECKING:
|
|
112
|
+
CursorCls = TypeVar("CursorCls", bound=SnowflakeCursorBase, default=SnowflakeCursor)
|
|
113
|
+
else:
|
|
114
|
+
CursorCls = TypeVar("CursorCls", bound=SnowflakeCursorBase)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class SnowflakeConnection(SnowflakeConnectionSync):
|
|
118
|
+
OCSP_ENV_LOCK = asyncio.Lock()
|
|
119
|
+
|
|
120
|
+
def __init__(
|
|
121
|
+
self,
|
|
122
|
+
connection_name: str | None = None,
|
|
123
|
+
connections_file_path: pathlib.Path | None = None,
|
|
124
|
+
**kwargs,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""Create a new SnowflakeConnection.
|
|
127
|
+
|
|
128
|
+
Connections can be loaded from the TOML file located at
|
|
129
|
+
snowflake.connector.constants.CONNECTIONS_FILE.
|
|
130
|
+
|
|
131
|
+
When connection_name is supplied we will first load that connection
|
|
132
|
+
and then override any other values supplied.
|
|
133
|
+
|
|
134
|
+
When no arguments are given (other than connection_file_path) the
|
|
135
|
+
default connection will be loaded first. Note that no overwriting is
|
|
136
|
+
supported in this case.
|
|
137
|
+
|
|
138
|
+
If overwriting values from the default connection is desirable, supply
|
|
139
|
+
the name explicitly.
|
|
140
|
+
"""
|
|
141
|
+
# note we don't call super here because asyncio can not/is not recommended
|
|
142
|
+
# to perform async operation in the __init__ while in the sync connection we
|
|
143
|
+
# perform connect
|
|
144
|
+
|
|
145
|
+
self._conn_parameters = self._init_connection_parameters(
|
|
146
|
+
kwargs, connection_name, connections_file_path
|
|
147
|
+
)
|
|
148
|
+
# SNOW-2352456: disable endpoint-based platform detection queries for async connection
|
|
149
|
+
if "platform_detection_timeout_seconds" not in kwargs:
|
|
150
|
+
self._platform_detection_timeout_seconds = 0.0
|
|
151
|
+
|
|
152
|
+
self.expired = False
|
|
153
|
+
# check SNOW-1218851 for long term improvement plan to refactor ocsp code
|
|
154
|
+
atexit.register(self._close_at_exit)
|
|
155
|
+
|
|
156
|
+
# Set up the file operation parser and stream downloader.
|
|
157
|
+
self._file_operation_parser = FileOperationParser(self)
|
|
158
|
+
self._stream_downloader = StreamDownloader(self)
|
|
159
|
+
self._snowflake_version: str | None = None
|
|
160
|
+
|
|
161
|
+
# CRL is disabled for async connection
|
|
162
|
+
self._crl_config = None # default value = disabled
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
async def snowflake_version(self) -> str:
|
|
166
|
+
# The result from SELECT CURRENT_VERSION() is `<version> <internal hash>`,
|
|
167
|
+
# and we only need the first part
|
|
168
|
+
if self._snowflake_version is None:
|
|
169
|
+
self._snowflake_version = str(
|
|
170
|
+
(
|
|
171
|
+
await (
|
|
172
|
+
await self.cursor().execute("SELECT CURRENT_VERSION()")
|
|
173
|
+
).fetchall()
|
|
174
|
+
)[0][0]
|
|
175
|
+
).split(" ")[0]
|
|
176
|
+
|
|
177
|
+
return self._snowflake_version
|
|
178
|
+
|
|
179
|
+
def __enter__(self):
|
|
180
|
+
# async connection does not support sync context manager
|
|
181
|
+
raise TypeError(
|
|
182
|
+
"'SnowflakeConnection' object does not support the context manager protocol"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
186
|
+
# async connection does not support sync context manager
|
|
187
|
+
raise TypeError(
|
|
188
|
+
"'SnowflakeConnection' object does not support the context manager protocol"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
async def __aenter__(self) -> SnowflakeConnection:
|
|
192
|
+
"""Context manager."""
|
|
193
|
+
# Idempotent __aenter__ - required to be able to use both:
|
|
194
|
+
# - with snowflake.connector.aio.SnowflakeConnection(**k)
|
|
195
|
+
# - with snowflake.connector.aio.connect(**k)
|
|
196
|
+
if self.is_closed():
|
|
197
|
+
await self.connect()
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
async def __aexit__(
|
|
201
|
+
self,
|
|
202
|
+
exc_type: type[BaseException] | None,
|
|
203
|
+
exc_val: BaseException | None,
|
|
204
|
+
exc_tb: TracebackType | None,
|
|
205
|
+
) -> None:
|
|
206
|
+
"""Context manager with commit or rollback teardown."""
|
|
207
|
+
if not self._session_parameters.get("AUTOCOMMIT", False):
|
|
208
|
+
# Either AUTOCOMMIT is turned off, or is not set so we default to old behavior
|
|
209
|
+
if exc_tb is None:
|
|
210
|
+
await self.commit()
|
|
211
|
+
else:
|
|
212
|
+
await self.rollback()
|
|
213
|
+
await self.close()
|
|
214
|
+
|
|
215
|
+
async def __open_connection(self):
|
|
216
|
+
"""Opens a new network connection."""
|
|
217
|
+
self.converter = self._converter_class(
|
|
218
|
+
use_numpy=self._numpy, support_negative_year=self._support_negative_year
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
self._rest = SnowflakeRestful(
|
|
222
|
+
host=self.host,
|
|
223
|
+
port=self.port,
|
|
224
|
+
protocol=self._protocol,
|
|
225
|
+
inject_client_pause=self._inject_client_pause,
|
|
226
|
+
connection=self,
|
|
227
|
+
session_manager=self._session_manager, # connection shares the session pool used for making Backend related requests
|
|
228
|
+
)
|
|
229
|
+
logger.debug("REST API object was created: %s:%s", self.host, self.port)
|
|
230
|
+
|
|
231
|
+
if "SF_OCSP_RESPONSE_CACHE_SERVER_URL" in os.environ:
|
|
232
|
+
logger.debug(
|
|
233
|
+
"Custom OCSP Cache Server URL found in environment - %s",
|
|
234
|
+
os.environ["SF_OCSP_RESPONSE_CACHE_SERVER_URL"],
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if ".privatelink.snowflakecomputing." in self.host.lower():
|
|
238
|
+
await SnowflakeConnection.setup_ocsp_privatelink(
|
|
239
|
+
self.application, self.host
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
if "SF_OCSP_RESPONSE_CACHE_SERVER_URL" in os.environ:
|
|
243
|
+
del os.environ["SF_OCSP_RESPONSE_CACHE_SERVER_URL"]
|
|
244
|
+
|
|
245
|
+
if self._session_parameters is None:
|
|
246
|
+
self._session_parameters = {}
|
|
247
|
+
if self._autocommit is not None:
|
|
248
|
+
self._session_parameters[PARAMETER_AUTOCOMMIT] = self._autocommit
|
|
249
|
+
|
|
250
|
+
if self._timezone is not None:
|
|
251
|
+
self._session_parameters[PARAMETER_TIMEZONE] = self._timezone
|
|
252
|
+
|
|
253
|
+
if self._validate_default_parameters:
|
|
254
|
+
# Snowflake will validate the requested database, schema, and warehouse
|
|
255
|
+
self._session_parameters[PARAMETER_CLIENT_VALIDATE_DEFAULT_PARAMETERS] = (
|
|
256
|
+
True
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if self.client_session_keep_alive is not None:
|
|
260
|
+
self._session_parameters[PARAMETER_CLIENT_SESSION_KEEP_ALIVE] = (
|
|
261
|
+
self._client_session_keep_alive
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if self.client_session_keep_alive_heartbeat_frequency is not None:
|
|
265
|
+
self._session_parameters[
|
|
266
|
+
PARAMETER_CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY
|
|
267
|
+
] = self._validate_client_session_keep_alive_heartbeat_frequency()
|
|
268
|
+
|
|
269
|
+
if self.client_prefetch_threads:
|
|
270
|
+
self._session_parameters[PARAMETER_CLIENT_PREFETCH_THREADS] = (
|
|
271
|
+
self._validate_client_prefetch_threads()
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Setup authenticator - validation happens in __config
|
|
275
|
+
auth = Auth(self.rest)
|
|
276
|
+
|
|
277
|
+
if self._session_token and self._master_token:
|
|
278
|
+
await auth._rest.update_tokens(
|
|
279
|
+
self._session_token,
|
|
280
|
+
self._master_token,
|
|
281
|
+
self._master_validity_in_seconds,
|
|
282
|
+
)
|
|
283
|
+
heartbeat_ret = await auth._rest._heartbeat()
|
|
284
|
+
logger.debug(heartbeat_ret)
|
|
285
|
+
if not heartbeat_ret or not heartbeat_ret.get("success"):
|
|
286
|
+
Error.errorhandler_wrapper(
|
|
287
|
+
self,
|
|
288
|
+
None,
|
|
289
|
+
ProgrammingError,
|
|
290
|
+
{
|
|
291
|
+
"msg": "Session and master tokens invalid",
|
|
292
|
+
"errno": ER_INVALID_VALUE,
|
|
293
|
+
},
|
|
294
|
+
)
|
|
295
|
+
else:
|
|
296
|
+
logger.debug("Session and master token validation successful.")
|
|
297
|
+
|
|
298
|
+
else:
|
|
299
|
+
if self.auth_class is not None:
|
|
300
|
+
if type(
|
|
301
|
+
self.auth_class
|
|
302
|
+
) not in FIRST_PARTY_AUTHENTICATORS and not issubclass(
|
|
303
|
+
type(self.auth_class), AuthByKeyPair
|
|
304
|
+
):
|
|
305
|
+
raise TypeError("auth_class must be a child class of AuthByKeyPair")
|
|
306
|
+
self.auth_class = self.auth_class
|
|
307
|
+
elif self._authenticator == DEFAULT_AUTHENTICATOR:
|
|
308
|
+
self.auth_class = AuthByDefault(
|
|
309
|
+
password=self._password,
|
|
310
|
+
timeout=self.login_timeout,
|
|
311
|
+
backoff_generator=self._backoff_generator,
|
|
312
|
+
)
|
|
313
|
+
elif self._authenticator == EXTERNAL_BROWSER_AUTHENTICATOR:
|
|
314
|
+
self._session_parameters[
|
|
315
|
+
PARAMETER_CLIENT_STORE_TEMPORARY_CREDENTIAL
|
|
316
|
+
] = (self._client_store_temporary_credential if IS_LINUX else True)
|
|
317
|
+
auth.read_temporary_credentials(
|
|
318
|
+
self.host,
|
|
319
|
+
self.user,
|
|
320
|
+
self._session_parameters,
|
|
321
|
+
)
|
|
322
|
+
# Depending on whether self._rest.id_token is available we do different
|
|
323
|
+
# auth_instance
|
|
324
|
+
if self._rest.id_token is None:
|
|
325
|
+
self.auth_class = AuthByWebBrowser(
|
|
326
|
+
application=self.application,
|
|
327
|
+
protocol=self._protocol,
|
|
328
|
+
host=self.host,
|
|
329
|
+
port=self.port,
|
|
330
|
+
timeout=self.login_timeout,
|
|
331
|
+
backoff_generator=self._backoff_generator,
|
|
332
|
+
)
|
|
333
|
+
else:
|
|
334
|
+
self.auth_class = AuthByIdToken(
|
|
335
|
+
id_token=self._rest.id_token,
|
|
336
|
+
application=self.application,
|
|
337
|
+
protocol=self._protocol,
|
|
338
|
+
host=self.host,
|
|
339
|
+
port=self.port,
|
|
340
|
+
timeout=self.login_timeout,
|
|
341
|
+
backoff_generator=self._backoff_generator,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
elif self._authenticator == KEY_PAIR_AUTHENTICATOR:
|
|
345
|
+
private_key = self._private_key
|
|
346
|
+
private_key_passphrase = self._private_key_passphrase
|
|
347
|
+
|
|
348
|
+
if self._private_key_file:
|
|
349
|
+
private_key = _get_private_bytes_from_file(
|
|
350
|
+
self._private_key_file,
|
|
351
|
+
self._private_key_file_pwd,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
self.auth_class = AuthByKeyPair(
|
|
355
|
+
private_key=private_key,
|
|
356
|
+
private_key_passphrase=private_key_passphrase,
|
|
357
|
+
timeout=self.login_timeout,
|
|
358
|
+
backoff_generator=self._backoff_generator,
|
|
359
|
+
)
|
|
360
|
+
elif self._authenticator == OAUTH_AUTHENTICATOR:
|
|
361
|
+
self.auth_class = AuthByOAuth(
|
|
362
|
+
oauth_token=self._token,
|
|
363
|
+
timeout=self.login_timeout,
|
|
364
|
+
backoff_generator=self._backoff_generator,
|
|
365
|
+
)
|
|
366
|
+
elif self._authenticator == OAUTH_AUTHORIZATION_CODE:
|
|
367
|
+
if self._role and (self._oauth_scope == ""):
|
|
368
|
+
# if role is known then let's inject it into scope
|
|
369
|
+
self._oauth_scope = _OAUTH_DEFAULT_SCOPE.format(role=self._role)
|
|
370
|
+
self.auth_class = AuthByOauthCode(
|
|
371
|
+
application=self.application,
|
|
372
|
+
client_id=self._oauth_client_id,
|
|
373
|
+
client_secret=self._oauth_client_secret,
|
|
374
|
+
host=self.host,
|
|
375
|
+
authentication_url=self._oauth_authorization_url.format(
|
|
376
|
+
host=self.host, port=self.port
|
|
377
|
+
),
|
|
378
|
+
token_request_url=self._oauth_token_request_url.format(
|
|
379
|
+
host=self.host, port=self.port
|
|
380
|
+
),
|
|
381
|
+
redirect_uri=self._oauth_redirect_uri,
|
|
382
|
+
uri=self._oauth_socket_uri,
|
|
383
|
+
scope=self._oauth_scope,
|
|
384
|
+
pkce_enabled=not self._oauth_disable_pkce,
|
|
385
|
+
token_cache=(
|
|
386
|
+
auth.get_token_cache()
|
|
387
|
+
if self._client_store_temporary_credential
|
|
388
|
+
else None
|
|
389
|
+
),
|
|
390
|
+
refresh_token_enabled=self._oauth_enable_refresh_tokens,
|
|
391
|
+
external_browser_timeout=self._external_browser_timeout,
|
|
392
|
+
enable_single_use_refresh_tokens=self._oauth_enable_single_use_refresh_tokens,
|
|
393
|
+
)
|
|
394
|
+
elif self._authenticator == OAUTH_CLIENT_CREDENTIALS:
|
|
395
|
+
if self._role and (self._oauth_scope == ""):
|
|
396
|
+
# if role is known then let's inject it into scope
|
|
397
|
+
self._oauth_scope = _OAUTH_DEFAULT_SCOPE.format(role=self._role)
|
|
398
|
+
self.auth_class = AuthByOauthCredentials(
|
|
399
|
+
application=self.application,
|
|
400
|
+
client_id=self._oauth_client_id,
|
|
401
|
+
client_secret=self._oauth_client_secret,
|
|
402
|
+
token_request_url=self._oauth_token_request_url.format(
|
|
403
|
+
host=self.host, port=self.port
|
|
404
|
+
),
|
|
405
|
+
scope=self._oauth_scope,
|
|
406
|
+
connection=self,
|
|
407
|
+
credentials_in_body=self._oauth_credentials_in_body,
|
|
408
|
+
)
|
|
409
|
+
elif self._authenticator == PROGRAMMATIC_ACCESS_TOKEN:
|
|
410
|
+
self.auth_class = AuthByPAT(self._token)
|
|
411
|
+
elif self._authenticator == PAT_WITH_EXTERNAL_SESSION:
|
|
412
|
+
# TODO: SNOW-2344581: add support for PAT with external session ID for async connection
|
|
413
|
+
raise ProgrammingError(
|
|
414
|
+
msg="PAT with external session ID is not supported for async connection.",
|
|
415
|
+
errno=ER_INVALID_VALUE,
|
|
416
|
+
)
|
|
417
|
+
elif self._authenticator == USR_PWD_MFA_AUTHENTICATOR:
|
|
418
|
+
self._session_parameters[PARAMETER_CLIENT_REQUEST_MFA_TOKEN] = (
|
|
419
|
+
self._client_request_mfa_token if IS_LINUX else True
|
|
420
|
+
)
|
|
421
|
+
if self._session_parameters[PARAMETER_CLIENT_REQUEST_MFA_TOKEN]:
|
|
422
|
+
auth.read_temporary_credentials(
|
|
423
|
+
self.host,
|
|
424
|
+
self.user,
|
|
425
|
+
self._session_parameters,
|
|
426
|
+
)
|
|
427
|
+
self.auth_class = AuthByUsrPwdMfa(
|
|
428
|
+
password=self._password,
|
|
429
|
+
mfa_token=self.rest.mfa_token,
|
|
430
|
+
timeout=self.login_timeout,
|
|
431
|
+
backoff_generator=self._backoff_generator,
|
|
432
|
+
)
|
|
433
|
+
elif self._authenticator == WORKLOAD_IDENTITY_AUTHENTICATOR:
|
|
434
|
+
if isinstance(self._workload_identity_provider, str):
|
|
435
|
+
self._workload_identity_provider = AttestationProvider.from_string(
|
|
436
|
+
self._workload_identity_provider
|
|
437
|
+
)
|
|
438
|
+
if not self._workload_identity_provider:
|
|
439
|
+
Error.errorhandler_wrapper(
|
|
440
|
+
self,
|
|
441
|
+
None,
|
|
442
|
+
ProgrammingError,
|
|
443
|
+
{
|
|
444
|
+
"msg": f"workload_identity_provider must be set to one of {','.join(AttestationProvider.all_string_values())} when authenticator is WORKLOAD_IDENTITY.",
|
|
445
|
+
"errno": ER_INVALID_WIF_SETTINGS,
|
|
446
|
+
},
|
|
447
|
+
)
|
|
448
|
+
if (
|
|
449
|
+
self._workload_identity_impersonation_path
|
|
450
|
+
and self._workload_identity_provider
|
|
451
|
+
not in (
|
|
452
|
+
AttestationProvider.GCP,
|
|
453
|
+
AttestationProvider.AWS,
|
|
454
|
+
)
|
|
455
|
+
):
|
|
456
|
+
Error.errorhandler_wrapper(
|
|
457
|
+
self,
|
|
458
|
+
None,
|
|
459
|
+
ProgrammingError,
|
|
460
|
+
{
|
|
461
|
+
"msg": "workload_identity_impersonation_path is currently only supported for GCP and AWS.",
|
|
462
|
+
"errno": ER_INVALID_WIF_SETTINGS,
|
|
463
|
+
},
|
|
464
|
+
)
|
|
465
|
+
self.auth_class = AuthByWorkloadIdentity(
|
|
466
|
+
provider=self._workload_identity_provider,
|
|
467
|
+
token=self._token,
|
|
468
|
+
entra_resource=self._workload_identity_entra_resource,
|
|
469
|
+
impersonation_path=self._workload_identity_impersonation_path,
|
|
470
|
+
)
|
|
471
|
+
else:
|
|
472
|
+
# okta URL, e.g., https://<account>.okta.com/
|
|
473
|
+
self.auth_class = AuthByOkta(
|
|
474
|
+
application=self.application,
|
|
475
|
+
timeout=self.login_timeout,
|
|
476
|
+
backoff_generator=self._backoff_generator,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
await self.authenticate_with_retry(self.auth_class)
|
|
480
|
+
|
|
481
|
+
self._password = None # ensure password won't persist
|
|
482
|
+
await self.auth_class.reset_secrets()
|
|
483
|
+
|
|
484
|
+
self.initialize_query_context_cache()
|
|
485
|
+
|
|
486
|
+
if self.client_session_keep_alive:
|
|
487
|
+
# This will be called after the heartbeat frequency has actually been set.
|
|
488
|
+
# By this point it should have been decided if the heartbeat has to be enabled
|
|
489
|
+
# and what would the heartbeat frequency be
|
|
490
|
+
await self._add_heartbeat()
|
|
491
|
+
|
|
492
|
+
async def _add_heartbeat(self) -> None:
|
|
493
|
+
"""Add a periodic heartbeat query in order to keep connection alive."""
|
|
494
|
+
if not self._heartbeat_task:
|
|
495
|
+
self._heartbeat_task = HeartBeatTimer(
|
|
496
|
+
self.client_session_keep_alive_heartbeat_frequency, self._heartbeat_tick
|
|
497
|
+
)
|
|
498
|
+
await self._heartbeat_task.start()
|
|
499
|
+
logger.debug("started heartbeat")
|
|
500
|
+
|
|
501
|
+
async def _heartbeat_tick(self) -> None:
|
|
502
|
+
"""Execute a heartbeat if connection isn't closed yet."""
|
|
503
|
+
if not self.is_closed():
|
|
504
|
+
logger.debug("heartbeating!")
|
|
505
|
+
await self.rest._heartbeat()
|
|
506
|
+
|
|
507
|
+
async def _all_async_queries_finished(self) -> bool:
|
|
508
|
+
"""Checks whether all async queries started by this Connection have finished executing."""
|
|
509
|
+
|
|
510
|
+
if not self._async_sfqids:
|
|
511
|
+
return True
|
|
512
|
+
|
|
513
|
+
queries = list(reversed(self._async_sfqids.keys()))
|
|
514
|
+
|
|
515
|
+
found_unfinished_query = False
|
|
516
|
+
|
|
517
|
+
async def async_query_check_helper(
|
|
518
|
+
sfq_id: str,
|
|
519
|
+
) -> bool:
|
|
520
|
+
try:
|
|
521
|
+
nonlocal found_unfinished_query
|
|
522
|
+
return found_unfinished_query or self.is_still_running(
|
|
523
|
+
await self.get_query_status(sfq_id)
|
|
524
|
+
)
|
|
525
|
+
except asyncio.CancelledError:
|
|
526
|
+
pass
|
|
527
|
+
|
|
528
|
+
tasks = [
|
|
529
|
+
asyncio.create_task(async_query_check_helper(sfqid)) for sfqid in queries
|
|
530
|
+
]
|
|
531
|
+
for task in asyncio.as_completed(tasks):
|
|
532
|
+
if await task:
|
|
533
|
+
found_unfinished_query = True
|
|
534
|
+
break
|
|
535
|
+
for task in tasks:
|
|
536
|
+
task.cancel()
|
|
537
|
+
await asyncio.gather(*tasks)
|
|
538
|
+
return not found_unfinished_query
|
|
539
|
+
|
|
540
|
+
async def _authenticate(self, auth_instance: AuthByPlugin):
|
|
541
|
+
await auth_instance.prepare(
|
|
542
|
+
conn=self,
|
|
543
|
+
authenticator=self._authenticator,
|
|
544
|
+
service_name=self.service_name,
|
|
545
|
+
account=self.account,
|
|
546
|
+
user=self.user,
|
|
547
|
+
password=self._password,
|
|
548
|
+
)
|
|
549
|
+
self._consent_cache_id_token = getattr(
|
|
550
|
+
auth_instance, "consent_cache_id_token", True
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
auth = Auth(self.rest)
|
|
554
|
+
# record start time for computing timeout
|
|
555
|
+
auth_instance._retry_ctx.set_start_time()
|
|
556
|
+
try:
|
|
557
|
+
await auth.authenticate(
|
|
558
|
+
auth_instance=auth_instance,
|
|
559
|
+
account=self.account,
|
|
560
|
+
user=self.user,
|
|
561
|
+
database=self.database,
|
|
562
|
+
schema=self.schema,
|
|
563
|
+
warehouse=self.warehouse,
|
|
564
|
+
role=self.role,
|
|
565
|
+
passcode=self._passcode,
|
|
566
|
+
passcode_in_password=self._passcode_in_password,
|
|
567
|
+
mfa_callback=self._mfa_callback,
|
|
568
|
+
password_callback=self._password_callback,
|
|
569
|
+
session_parameters=self._session_parameters,
|
|
570
|
+
)
|
|
571
|
+
except OperationalError as e:
|
|
572
|
+
logger.debug(
|
|
573
|
+
"Operational Error raised at authentication"
|
|
574
|
+
f"for authenticator: {type(auth_instance).__name__}"
|
|
575
|
+
)
|
|
576
|
+
while True:
|
|
577
|
+
try:
|
|
578
|
+
await auth_instance.handle_timeout(
|
|
579
|
+
authenticator=self._authenticator,
|
|
580
|
+
service_name=self.service_name,
|
|
581
|
+
account=self.account,
|
|
582
|
+
user=self.user,
|
|
583
|
+
password=self._password,
|
|
584
|
+
)
|
|
585
|
+
await auth.authenticate(
|
|
586
|
+
auth_instance=auth_instance,
|
|
587
|
+
account=self.account,
|
|
588
|
+
user=self.user,
|
|
589
|
+
database=self.database,
|
|
590
|
+
schema=self.schema,
|
|
591
|
+
warehouse=self.warehouse,
|
|
592
|
+
role=self.role,
|
|
593
|
+
passcode=self._passcode,
|
|
594
|
+
passcode_in_password=self._passcode_in_password,
|
|
595
|
+
mfa_callback=self._mfa_callback,
|
|
596
|
+
password_callback=self._password_callback,
|
|
597
|
+
session_parameters=self._session_parameters,
|
|
598
|
+
)
|
|
599
|
+
except OperationalError as auth_op:
|
|
600
|
+
if auth_op.errno == ER_FAILED_TO_CONNECT_TO_DB:
|
|
601
|
+
if _CONNECTIVITY_ERR_MSG in e.msg:
|
|
602
|
+
auth_op.msg += f"\n{_CONNECTIVITY_ERR_MSG}"
|
|
603
|
+
raise auth_op from e
|
|
604
|
+
logger.debug("Continuing authenticator specific timeout handling")
|
|
605
|
+
continue
|
|
606
|
+
break
|
|
607
|
+
|
|
608
|
+
async def _cancel_heartbeat(self) -> None:
|
|
609
|
+
"""Cancel a heartbeat thread."""
|
|
610
|
+
if self._heartbeat_task:
|
|
611
|
+
await self._heartbeat_task.stop()
|
|
612
|
+
self._heartbeat_task = None
|
|
613
|
+
logger.debug("stopped heartbeat")
|
|
614
|
+
|
|
615
|
+
def _init_connection_parameters(
|
|
616
|
+
self,
|
|
617
|
+
connection_init_kwargs: dict,
|
|
618
|
+
connection_name: str | None = None,
|
|
619
|
+
connections_file_path: pathlib.Path | None = None,
|
|
620
|
+
) -> dict:
|
|
621
|
+
ret_kwargs = connection_init_kwargs
|
|
622
|
+
self._unsafe_skip_file_permissions_check = ret_kwargs.get(
|
|
623
|
+
"unsafe_skip_file_permissions_check", False
|
|
624
|
+
)
|
|
625
|
+
easy_logging = EasyLoggingConfigPython(
|
|
626
|
+
skip_config_file_permissions_check=self._unsafe_skip_file_permissions_check
|
|
627
|
+
)
|
|
628
|
+
easy_logging.create_log()
|
|
629
|
+
self._lock_sequence_counter = asyncio.Lock()
|
|
630
|
+
self.sequence_counter = 0
|
|
631
|
+
self._errorhandler = Error.default_errorhandler
|
|
632
|
+
self._lock_converter = asyncio.Lock()
|
|
633
|
+
self.messages = []
|
|
634
|
+
self._async_sfqids: dict[str, None] = {}
|
|
635
|
+
self._done_async_sfqids: dict[str, None] = {}
|
|
636
|
+
self._client_param_telemetry_enabled = True
|
|
637
|
+
self._server_param_telemetry_enabled = False
|
|
638
|
+
self._session_parameters: dict[str, str | int | bool] = {}
|
|
639
|
+
logger.info(
|
|
640
|
+
"Snowflake Connector for Python Version: %s, "
|
|
641
|
+
"Python Version: %s, Platform: %s",
|
|
642
|
+
SNOWFLAKE_CONNECTOR_VERSION,
|
|
643
|
+
PYTHON_VERSION,
|
|
644
|
+
PLATFORM,
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
# Placeholder attributes; will be initialized in connect()
|
|
648
|
+
self._http_config: AioHttpConfig | None = None
|
|
649
|
+
self._session_manager: SessionManager | None = None
|
|
650
|
+
self._rest = None
|
|
651
|
+
for name, (value, _) in DEFAULT_CONFIGURATION.items():
|
|
652
|
+
setattr(self, f"_{name}", value)
|
|
653
|
+
|
|
654
|
+
self._heartbeat_task = None
|
|
655
|
+
is_kwargs_empty = not connection_init_kwargs
|
|
656
|
+
|
|
657
|
+
if "application" not in connection_init_kwargs:
|
|
658
|
+
app = self._detect_application()
|
|
659
|
+
if app:
|
|
660
|
+
connection_init_kwargs["application"] = app
|
|
661
|
+
|
|
662
|
+
if "insecure_mode" in connection_init_kwargs:
|
|
663
|
+
warn_message = "The 'insecure_mode' connection property is deprecated. Please use 'disable_ocsp_checks' instead"
|
|
664
|
+
warnings.warn(
|
|
665
|
+
warn_message,
|
|
666
|
+
DeprecationWarning,
|
|
667
|
+
stacklevel=2,
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
if (
|
|
671
|
+
"disable_ocsp_checks" in connection_init_kwargs
|
|
672
|
+
and connection_init_kwargs["disable_ocsp_checks"]
|
|
673
|
+
!= connection_init_kwargs["insecure_mode"]
|
|
674
|
+
):
|
|
675
|
+
logger.warning(
|
|
676
|
+
"The values for 'disable_ocsp_checks' and 'insecure_mode' differ. "
|
|
677
|
+
"Using the value of 'disable_ocsp_checks."
|
|
678
|
+
)
|
|
679
|
+
else:
|
|
680
|
+
self._disable_ocsp_checks = connection_init_kwargs["insecure_mode"]
|
|
681
|
+
|
|
682
|
+
self.converter = None
|
|
683
|
+
self.query_context_cache: QueryContextCache | None = None
|
|
684
|
+
self.query_context_cache_size = 5
|
|
685
|
+
if connections_file_path is not None:
|
|
686
|
+
# Change config file path and force update cache
|
|
687
|
+
for i, s in enumerate(CONFIG_MANAGER._slices):
|
|
688
|
+
if s.section == "connections":
|
|
689
|
+
CONFIG_MANAGER._slices[i] = s._replace(path=connections_file_path)
|
|
690
|
+
CONFIG_MANAGER.read_config(
|
|
691
|
+
skip_file_permissions_check=self._unsafe_skip_file_permissions_check
|
|
692
|
+
)
|
|
693
|
+
break
|
|
694
|
+
if connection_name is not None:
|
|
695
|
+
connections = CONFIG_MANAGER["connections"]
|
|
696
|
+
if connection_name not in connections:
|
|
697
|
+
raise Error(
|
|
698
|
+
f"Invalid connection_name '{connection_name}',"
|
|
699
|
+
f" known ones are {list(connections.keys())}"
|
|
700
|
+
)
|
|
701
|
+
ret_kwargs = {**connections[connection_name], **connection_init_kwargs}
|
|
702
|
+
elif is_kwargs_empty:
|
|
703
|
+
# connection_name is None and kwargs was empty when called
|
|
704
|
+
ret_kwargs = _get_default_connection_params()
|
|
705
|
+
# TODO: SNOW-1770153 on self.__set_error_attributes()
|
|
706
|
+
return ret_kwargs
|
|
707
|
+
|
|
708
|
+
async def _cancel_query(
|
|
709
|
+
self, sql: str, request_id: uuid.UUID
|
|
710
|
+
) -> dict[str, bool | None]:
|
|
711
|
+
"""Cancels the query with the exact SQL query and requestId."""
|
|
712
|
+
logger.debug("_cancel_query sql=[%s], request_id=[%s]", sql, request_id)
|
|
713
|
+
url_parameters = {REQUEST_ID: str(uuid.uuid4())}
|
|
714
|
+
|
|
715
|
+
return await self.rest.request(
|
|
716
|
+
"/queries/v1/abort-request?" + urlencode(url_parameters),
|
|
717
|
+
{
|
|
718
|
+
"sqlText": sql,
|
|
719
|
+
REQUEST_ID: str(request_id),
|
|
720
|
+
},
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
def _close_at_exit(self):
|
|
724
|
+
with suppress(Exception):
|
|
725
|
+
asyncio.run(self.close(retry=False))
|
|
726
|
+
|
|
727
|
+
async def _get_query_status(
|
|
728
|
+
self, sf_qid: str
|
|
729
|
+
) -> tuple[QueryStatus, dict[str, Any]]:
|
|
730
|
+
"""Retrieves the status of query with sf_qid and returns it with the raw response.
|
|
731
|
+
|
|
732
|
+
This is the underlying function used by the public get_status functions.
|
|
733
|
+
|
|
734
|
+
Args:
|
|
735
|
+
sf_qid: Snowflake query id of interest.
|
|
736
|
+
|
|
737
|
+
Raises:
|
|
738
|
+
ValueError: if sf_qid is not a valid UUID string.
|
|
739
|
+
"""
|
|
740
|
+
try:
|
|
741
|
+
uuid.UUID(sf_qid)
|
|
742
|
+
except ValueError:
|
|
743
|
+
raise ValueError(f"Invalid UUID: '{sf_qid}'")
|
|
744
|
+
logger.debug(f"get_query_status sf_qid='{sf_qid}'")
|
|
745
|
+
|
|
746
|
+
status = "NO_DATA"
|
|
747
|
+
if self.is_closed():
|
|
748
|
+
return QueryStatus.DISCONNECTED, {"data": {"queries": []}}
|
|
749
|
+
status_resp = await self.rest.request(
|
|
750
|
+
"/monitoring/queries/" + quote(sf_qid), method="get", client="rest"
|
|
751
|
+
)
|
|
752
|
+
if "queries" not in status_resp["data"]:
|
|
753
|
+
return QueryStatus.FAILED_WITH_ERROR, status_resp
|
|
754
|
+
queries = status_resp["data"]["queries"]
|
|
755
|
+
if len(queries) > 0:
|
|
756
|
+
status = queries[0]["status"]
|
|
757
|
+
status_ret = QueryStatus[status]
|
|
758
|
+
return status_ret, status_resp
|
|
759
|
+
|
|
760
|
+
async def _log_telemetry(self, telemetry_data) -> None:
|
|
761
|
+
if self.telemetry_enabled:
|
|
762
|
+
await self._telemetry.try_add_log_to_batch(telemetry_data)
|
|
763
|
+
|
|
764
|
+
async def _log_telemetry_imported_packages(self) -> None:
|
|
765
|
+
if self._log_imported_packages_in_telemetry:
|
|
766
|
+
# filter out duplicates caused by submodules
|
|
767
|
+
# and internal modules with names starting with an underscore
|
|
768
|
+
imported_modules = {
|
|
769
|
+
k.split(".", maxsplit=1)[0]
|
|
770
|
+
for k in list(sys.modules)
|
|
771
|
+
if not k.startswith("_")
|
|
772
|
+
}
|
|
773
|
+
ts = get_time_millis()
|
|
774
|
+
await self._log_telemetry(
|
|
775
|
+
TelemetryData.from_telemetry_data_dict(
|
|
776
|
+
from_dict={
|
|
777
|
+
TelemetryField.KEY_TYPE.value: TelemetryField.IMPORTED_PACKAGES.value,
|
|
778
|
+
TelemetryField.KEY_VALUE.value: str(imported_modules),
|
|
779
|
+
},
|
|
780
|
+
timestamp=ts,
|
|
781
|
+
connection=self,
|
|
782
|
+
)
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
async def _next_sequence_counter(self) -> int:
|
|
786
|
+
"""Gets next sequence counter. Used internally."""
|
|
787
|
+
async with self._lock_sequence_counter:
|
|
788
|
+
self.sequence_counter += 1
|
|
789
|
+
logger.debug("sequence counter: %s", self.sequence_counter)
|
|
790
|
+
return self.sequence_counter
|
|
791
|
+
|
|
792
|
+
async def _update_parameters(
|
|
793
|
+
self,
|
|
794
|
+
parameters: dict[str, str | int | bool],
|
|
795
|
+
) -> None:
|
|
796
|
+
"""Update session parameters."""
|
|
797
|
+
async with self._lock_converter:
|
|
798
|
+
self.converter.set_parameters(parameters)
|
|
799
|
+
for name, value in parameters.items():
|
|
800
|
+
self._session_parameters[name] = value
|
|
801
|
+
if PARAMETER_CLIENT_TELEMETRY_ENABLED == name:
|
|
802
|
+
self._server_param_telemetry_enabled = value
|
|
803
|
+
elif PARAMETER_CLIENT_SESSION_KEEP_ALIVE == name:
|
|
804
|
+
# Only set if the local config is None.
|
|
805
|
+
# Always give preference to user config.
|
|
806
|
+
if self.client_session_keep_alive is None:
|
|
807
|
+
self.client_session_keep_alive = value
|
|
808
|
+
elif (
|
|
809
|
+
PARAMETER_CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY == name
|
|
810
|
+
and self.client_session_keep_alive_heartbeat_frequency is None
|
|
811
|
+
):
|
|
812
|
+
# Only set if local value hasn't been set already.
|
|
813
|
+
self.client_session_keep_alive_heartbeat_frequency = value
|
|
814
|
+
elif PARAMETER_SERVICE_NAME == name:
|
|
815
|
+
self.service_name = value
|
|
816
|
+
elif PARAMETER_CLIENT_PREFETCH_THREADS == name:
|
|
817
|
+
self.client_prefetch_threads = value
|
|
818
|
+
elif PARAMETER_ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1 == name:
|
|
819
|
+
self.enable_stage_s3_privatelink_for_us_east_1 = value
|
|
820
|
+
elif PARAMETER_QUERY_CONTEXT_CACHE_SIZE == name:
|
|
821
|
+
self.query_context_cache_size = value
|
|
822
|
+
|
|
823
|
+
async def _reauthenticate(self):
|
|
824
|
+
return await self._auth_class.reauthenticate(conn=self)
|
|
825
|
+
|
|
826
|
+
@property
|
|
827
|
+
def auth_class(self) -> AuthByPlugin | None:
|
|
828
|
+
return self._auth_class
|
|
829
|
+
|
|
830
|
+
@auth_class.setter
|
|
831
|
+
def auth_class(self, value: AuthByPlugin) -> None:
|
|
832
|
+
if isinstance(value, AuthByPlugin):
|
|
833
|
+
self._auth_class = value
|
|
834
|
+
else:
|
|
835
|
+
raise TypeError("auth_class must subclass AuthByPluginAsync")
|
|
836
|
+
|
|
837
|
+
@property
|
|
838
|
+
def client_prefetch_threads(self) -> int:
|
|
839
|
+
return self._client_prefetch_threads
|
|
840
|
+
|
|
841
|
+
@client_prefetch_threads.setter
|
|
842
|
+
def client_prefetch_threads(self, value) -> None:
|
|
843
|
+
self._client_prefetch_threads = value
|
|
844
|
+
|
|
845
|
+
@property
|
|
846
|
+
def errorhandler(self) -> None:
|
|
847
|
+
# check SNOW-1763103
|
|
848
|
+
raise NotImplementedError(
|
|
849
|
+
"Async Snowflake Python Connector does not support errorhandler. "
|
|
850
|
+
"Please open a feature request issue in github if your want this feature: "
|
|
851
|
+
"https://github.com/snowflakedb/snowflake-connector-python/issues/new/choose."
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
@errorhandler.setter
|
|
855
|
+
def errorhandler(self, value) -> None:
|
|
856
|
+
# check SNOW-1763103
|
|
857
|
+
raise NotImplementedError(
|
|
858
|
+
"Async Snowflake Python Connector does not support errorhandler. "
|
|
859
|
+
"Please open a feature request issue in github if your want this feature: "
|
|
860
|
+
"https://github.com/snowflakedb/snowflake-connector-python/issues/new/choose."
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
@property
|
|
864
|
+
def rest(self) -> SnowflakeRestful | None:
|
|
865
|
+
return self._rest
|
|
866
|
+
|
|
867
|
+
async def authenticate_with_retry(self, auth_instance) -> None:
|
|
868
|
+
# make some changes if needed before real __authenticate
|
|
869
|
+
try:
|
|
870
|
+
await self._authenticate(auth_instance)
|
|
871
|
+
except ReauthenticationRequest as ex:
|
|
872
|
+
# cached id_token expiration error, we have cleaned id_token and try to authenticate again
|
|
873
|
+
logger.debug("ID token expired. Reauthenticating...: %s", ex)
|
|
874
|
+
if type(auth_instance) in (
|
|
875
|
+
AuthByIdToken,
|
|
876
|
+
AuthByOauthCode,
|
|
877
|
+
AuthByOauthCredentials,
|
|
878
|
+
):
|
|
879
|
+
# Note: SNOW-733835 IDToken auth needs to authenticate through
|
|
880
|
+
# SSO if it has expired
|
|
881
|
+
await self._reauthenticate()
|
|
882
|
+
else:
|
|
883
|
+
await self._authenticate(auth_instance)
|
|
884
|
+
|
|
885
|
+
async def autocommit(self, mode) -> None:
|
|
886
|
+
"""Sets autocommit mode to True, or False. Defaults to True."""
|
|
887
|
+
if not self.rest:
|
|
888
|
+
Error.errorhandler_wrapper(
|
|
889
|
+
self,
|
|
890
|
+
None,
|
|
891
|
+
DatabaseError,
|
|
892
|
+
{
|
|
893
|
+
"msg": "Connection is closed",
|
|
894
|
+
"errno": ER_CONNECTION_IS_CLOSED,
|
|
895
|
+
"sqlstate": SQLSTATE_CONNECTION_NOT_EXISTS,
|
|
896
|
+
},
|
|
897
|
+
)
|
|
898
|
+
if not isinstance(mode, bool):
|
|
899
|
+
Error.errorhandler_wrapper(
|
|
900
|
+
self,
|
|
901
|
+
None,
|
|
902
|
+
ProgrammingError,
|
|
903
|
+
{
|
|
904
|
+
"msg": f"Invalid parameter: {mode}",
|
|
905
|
+
"errno": ER_INVALID_VALUE,
|
|
906
|
+
},
|
|
907
|
+
)
|
|
908
|
+
try:
|
|
909
|
+
await self.cursor().execute(f"ALTER SESSION SET autocommit={mode}")
|
|
910
|
+
except Error as e:
|
|
911
|
+
if e.sqlstate == SQLSTATE_FEATURE_NOT_SUPPORTED:
|
|
912
|
+
logger.debug(
|
|
913
|
+
"Autocommit feature is not enabled for this " "connection. Ignored"
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
async def close(self, retry: bool = True) -> None:
|
|
917
|
+
"""Closes the connection."""
|
|
918
|
+
# unregister to dereference connection object as it's already closed after the execution
|
|
919
|
+
atexit.unregister(self._close_at_exit)
|
|
920
|
+
try:
|
|
921
|
+
if not self.rest:
|
|
922
|
+
logger.debug("Rest object has been destroyed, cannot close session")
|
|
923
|
+
return
|
|
924
|
+
|
|
925
|
+
# will hang if the application doesn't close the connection and
|
|
926
|
+
# CLIENT_SESSION_KEEP_ALIVE is set, because the heartbeat runs on
|
|
927
|
+
# a separate thread.
|
|
928
|
+
await self._cancel_heartbeat()
|
|
929
|
+
|
|
930
|
+
# close telemetry first, since it needs rest to send remaining data
|
|
931
|
+
logger.debug("closed")
|
|
932
|
+
|
|
933
|
+
if self.telemetry_enabled:
|
|
934
|
+
await self._telemetry.close(retry=retry)
|
|
935
|
+
|
|
936
|
+
if not self._server_session_keep_alive:
|
|
937
|
+
if await self._all_async_queries_finished():
|
|
938
|
+
logger.debug(
|
|
939
|
+
"No async queries seem to be running, deleting session"
|
|
940
|
+
)
|
|
941
|
+
try:
|
|
942
|
+
await self.rest.delete_session(retry=retry)
|
|
943
|
+
except Exception as e:
|
|
944
|
+
logger.debug(
|
|
945
|
+
"Exception encountered in deleting session. ignoring...: %s",
|
|
946
|
+
e,
|
|
947
|
+
)
|
|
948
|
+
else:
|
|
949
|
+
logger.debug(
|
|
950
|
+
"There are {} async queries still running, not deleting session".format(
|
|
951
|
+
len(self._async_sfqids)
|
|
952
|
+
)
|
|
953
|
+
)
|
|
954
|
+
else:
|
|
955
|
+
logger.info(
|
|
956
|
+
"Parameter server_session_keep_alive was set to True - skipping session logout. "
|
|
957
|
+
"If there are any not-finished queries in the current session (session_id: %s) - "
|
|
958
|
+
"they will continue to live in Snowflake and consume credits until they finish. "
|
|
959
|
+
"To cancel them use Monitoring tab in Snowsight or plain SQL.",
|
|
960
|
+
self.session_id,
|
|
961
|
+
)
|
|
962
|
+
await self.rest.close()
|
|
963
|
+
self._rest = None
|
|
964
|
+
if self.query_context_cache:
|
|
965
|
+
self.query_context_cache.clear_cache()
|
|
966
|
+
del self.messages[:]
|
|
967
|
+
logger.debug("Session is closed")
|
|
968
|
+
except Exception as e:
|
|
969
|
+
logger.debug(
|
|
970
|
+
"Exception encountered in closing connection. ignoring...: %s", e
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
async def cmd_query(
|
|
974
|
+
self,
|
|
975
|
+
sql: str,
|
|
976
|
+
sequence_counter: int,
|
|
977
|
+
request_id: uuid.UUID,
|
|
978
|
+
binding_params: None | tuple | dict[str, dict[str, str]] = None,
|
|
979
|
+
binding_stage: str | None = None,
|
|
980
|
+
is_file_transfer: bool = False,
|
|
981
|
+
statement_params: dict[str, str] | None = None,
|
|
982
|
+
is_internal: bool = False,
|
|
983
|
+
describe_only: bool = False,
|
|
984
|
+
_no_results: bool = False,
|
|
985
|
+
_update_current_object: bool = True,
|
|
986
|
+
_no_retry: bool = False,
|
|
987
|
+
timeout: int | None = None,
|
|
988
|
+
dataframe_ast: str | None = None,
|
|
989
|
+
) -> dict[str, Any]:
|
|
990
|
+
"""Executes a query with a sequence counter."""
|
|
991
|
+
logger.debug("_cmd_query")
|
|
992
|
+
data = {
|
|
993
|
+
"sqlText": sql,
|
|
994
|
+
"asyncExec": _no_results,
|
|
995
|
+
"sequenceId": sequence_counter,
|
|
996
|
+
"querySubmissionTime": get_time_millis(),
|
|
997
|
+
}
|
|
998
|
+
if dataframe_ast is not None:
|
|
999
|
+
data["dataframeAst"] = dataframe_ast
|
|
1000
|
+
if statement_params is not None:
|
|
1001
|
+
data["parameters"] = statement_params
|
|
1002
|
+
if is_internal:
|
|
1003
|
+
data["isInternal"] = is_internal
|
|
1004
|
+
if describe_only:
|
|
1005
|
+
data["describeOnly"] = describe_only
|
|
1006
|
+
if binding_stage is not None:
|
|
1007
|
+
# binding stage for bulk array binding
|
|
1008
|
+
data["bindStage"] = binding_stage
|
|
1009
|
+
if binding_params is not None:
|
|
1010
|
+
# binding parameters. This is for qmarks paramstyle.
|
|
1011
|
+
data["bindings"] = binding_params
|
|
1012
|
+
if not _no_results:
|
|
1013
|
+
# not an async query.
|
|
1014
|
+
queryContext = self.get_query_context()
|
|
1015
|
+
# Here queryContextDTO should be a dict object field, same with `parameters` field
|
|
1016
|
+
data["queryContextDTO"] = queryContext
|
|
1017
|
+
client = "sfsql_file_transfer" if is_file_transfer else "sfsql"
|
|
1018
|
+
|
|
1019
|
+
if logger.getEffectiveLevel() <= logging.DEBUG:
|
|
1020
|
+
logger.debug(
|
|
1021
|
+
"sql=[%s], sequence_id=[%s], is_file_transfer=[%s]",
|
|
1022
|
+
self._format_query_for_log(data["sqlText"]),
|
|
1023
|
+
data["sequenceId"],
|
|
1024
|
+
is_file_transfer,
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
url_parameters = {REQUEST_ID: request_id}
|
|
1028
|
+
|
|
1029
|
+
ret = await self.rest.request(
|
|
1030
|
+
"/queries/v1/query-request?" + urlencode(url_parameters),
|
|
1031
|
+
data,
|
|
1032
|
+
client=client,
|
|
1033
|
+
_no_results=_no_results,
|
|
1034
|
+
_include_retry_params=True,
|
|
1035
|
+
_no_retry=_no_retry,
|
|
1036
|
+
timeout=timeout,
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
if ret is None:
|
|
1040
|
+
ret = {"data": {}}
|
|
1041
|
+
if ret.get("data") is None:
|
|
1042
|
+
ret["data"] = {}
|
|
1043
|
+
if _update_current_object:
|
|
1044
|
+
data = ret["data"]
|
|
1045
|
+
if "finalDatabaseName" in data and data["finalDatabaseName"] is not None:
|
|
1046
|
+
self._database = data["finalDatabaseName"]
|
|
1047
|
+
if "finalSchemaName" in data and data["finalSchemaName"] is not None:
|
|
1048
|
+
self._schema = data["finalSchemaName"]
|
|
1049
|
+
if "finalWarehouseName" in data and data["finalWarehouseName"] is not None:
|
|
1050
|
+
self._warehouse = data["finalWarehouseName"]
|
|
1051
|
+
if "finalRoleName" in data:
|
|
1052
|
+
self._role = data["finalRoleName"]
|
|
1053
|
+
if "queryContext" in data and not _no_results:
|
|
1054
|
+
# here the data["queryContext"] field has been automatically converted from JSON into a dict type
|
|
1055
|
+
self.set_query_context(data["queryContext"])
|
|
1056
|
+
|
|
1057
|
+
return ret
|
|
1058
|
+
|
|
1059
|
+
async def commit(self) -> None:
|
|
1060
|
+
"""Commits the current transaction."""
|
|
1061
|
+
await self.cursor().execute("COMMIT")
|
|
1062
|
+
|
|
1063
|
+
async def connect(self, **kwargs) -> None:
|
|
1064
|
+
"""Establishes connection to Snowflake."""
|
|
1065
|
+
logger.debug("connect")
|
|
1066
|
+
if len(kwargs) > 0:
|
|
1067
|
+
self.__config(**kwargs)
|
|
1068
|
+
else:
|
|
1069
|
+
self.__config(**self._conn_parameters)
|
|
1070
|
+
|
|
1071
|
+
no_proxy_csv_str = (
|
|
1072
|
+
",".join(str(x) for x in self.no_proxy)
|
|
1073
|
+
if (
|
|
1074
|
+
self.no_proxy is not None
|
|
1075
|
+
and isinstance(self.no_proxy, Iterable)
|
|
1076
|
+
and not isinstance(self.no_proxy, (str, bytes))
|
|
1077
|
+
)
|
|
1078
|
+
else self.no_proxy
|
|
1079
|
+
)
|
|
1080
|
+
self._http_config: AioHttpConfig = AioHttpConfig(
|
|
1081
|
+
connector_factory=SnowflakeSSLConnectorFactory(),
|
|
1082
|
+
use_pooling=not self.disable_request_pooling,
|
|
1083
|
+
proxy_host=self.proxy_host,
|
|
1084
|
+
proxy_port=self.proxy_port,
|
|
1085
|
+
proxy_user=self.proxy_user,
|
|
1086
|
+
proxy_password=self.proxy_password,
|
|
1087
|
+
snowflake_ocsp_mode=self._ocsp_mode(),
|
|
1088
|
+
trust_env=True, # Required for proxy support via environment variables
|
|
1089
|
+
no_proxy=no_proxy_csv_str,
|
|
1090
|
+
)
|
|
1091
|
+
self._session_manager = SessionManagerFactory.get_manager(self._http_config)
|
|
1092
|
+
|
|
1093
|
+
if self.enable_connection_diag:
|
|
1094
|
+
raise NotImplementedError(
|
|
1095
|
+
"Connection diagnostic is not supported in asyncio"
|
|
1096
|
+
)
|
|
1097
|
+
else:
|
|
1098
|
+
await self.__open_connection()
|
|
1099
|
+
self._telemetry = TelemetryClient(self._rest)
|
|
1100
|
+
await self._log_telemetry_imported_packages()
|
|
1101
|
+
|
|
1102
|
+
def cursor(self, cursor_class: type[CursorCls] = SnowflakeCursor) -> CursorCls:
|
|
1103
|
+
logger.debug("cursor")
|
|
1104
|
+
if not self.rest:
|
|
1105
|
+
Error.errorhandler_wrapper(
|
|
1106
|
+
self,
|
|
1107
|
+
None,
|
|
1108
|
+
DatabaseError,
|
|
1109
|
+
{
|
|
1110
|
+
"msg": "Connection is closed.\nPlease establish the connection first by "
|
|
1111
|
+
"explicitly calling `await SnowflakeConnection.connect()` or "
|
|
1112
|
+
"using an async context manager: `async with SnowflakeConnection() as conn`. "
|
|
1113
|
+
"\nEnsure the connection is open before attempting any operations.",
|
|
1114
|
+
"errno": ER_CONNECTION_IS_CLOSED,
|
|
1115
|
+
"sqlstate": SQLSTATE_CONNECTION_NOT_EXISTS,
|
|
1116
|
+
},
|
|
1117
|
+
)
|
|
1118
|
+
return cursor_class(self)
|
|
1119
|
+
|
|
1120
|
+
async def execute_stream(
|
|
1121
|
+
self,
|
|
1122
|
+
stream: StringIO,
|
|
1123
|
+
remove_comments: bool = False,
|
|
1124
|
+
cursor_class: type[SnowflakeCursor] = SnowflakeCursor,
|
|
1125
|
+
**kwargs,
|
|
1126
|
+
) -> AsyncIterator[SnowflakeCursor, None, None]:
|
|
1127
|
+
"""Executes a stream of SQL statements. This is a non-standard convenient method."""
|
|
1128
|
+
split_statements_list = split_statements(
|
|
1129
|
+
stream, remove_comments=remove_comments
|
|
1130
|
+
)
|
|
1131
|
+
# Note: split_statements_list is a list of tuples of sql statements and whether they are put/get
|
|
1132
|
+
non_empty_statements = [e for e in split_statements_list if e[0]]
|
|
1133
|
+
for sql, is_put_or_get in non_empty_statements:
|
|
1134
|
+
cur = self.cursor(cursor_class=cursor_class)
|
|
1135
|
+
await cur.execute(sql, _is_put_get=is_put_or_get, **kwargs)
|
|
1136
|
+
yield cur
|
|
1137
|
+
|
|
1138
|
+
async def execute_string(
|
|
1139
|
+
self,
|
|
1140
|
+
sql_text: str,
|
|
1141
|
+
remove_comments: bool = False,
|
|
1142
|
+
return_cursors: bool = True,
|
|
1143
|
+
cursor_class: type[SnowflakeCursor] = SnowflakeCursor,
|
|
1144
|
+
**kwargs,
|
|
1145
|
+
) -> Iterable[SnowflakeCursor]:
|
|
1146
|
+
"""Executes a SQL text including multiple statements. This is a non-standard convenience method."""
|
|
1147
|
+
stream = StringIO(sql_text)
|
|
1148
|
+
ret = []
|
|
1149
|
+
async for cursor in self.execute_stream(
|
|
1150
|
+
stream, remove_comments=remove_comments, cursor_class=cursor_class, **kwargs
|
|
1151
|
+
):
|
|
1152
|
+
ret.append(cursor)
|
|
1153
|
+
|
|
1154
|
+
return ret if return_cursors else list()
|
|
1155
|
+
|
|
1156
|
+
async def get_query_status(self, sf_qid: str) -> QueryStatus:
|
|
1157
|
+
"""Retrieves the status of query with sf_qid.
|
|
1158
|
+
|
|
1159
|
+
Query status is returned as a QueryStatus.
|
|
1160
|
+
|
|
1161
|
+
Args:
|
|
1162
|
+
sf_qid: Snowflake query id of interest.
|
|
1163
|
+
|
|
1164
|
+
Raises:
|
|
1165
|
+
ValueError: if sf_qid is not a valid UUID string.
|
|
1166
|
+
"""
|
|
1167
|
+
status, _ = await self._get_query_status(sf_qid)
|
|
1168
|
+
self._cache_query_status(sf_qid, status)
|
|
1169
|
+
return status
|
|
1170
|
+
|
|
1171
|
+
async def get_query_status_throw_if_error(self, sf_qid: str) -> QueryStatus:
|
|
1172
|
+
"""Retrieves the status of query with sf_qid as a QueryStatus and raises an exception if the query terminated with an error.
|
|
1173
|
+
|
|
1174
|
+
Query status is returned as a QueryStatus.
|
|
1175
|
+
|
|
1176
|
+
Args:
|
|
1177
|
+
sf_qid: Snowflake query id of interest.
|
|
1178
|
+
|
|
1179
|
+
Raises:
|
|
1180
|
+
ValueError: if sf_qid is not a valid UUID string.
|
|
1181
|
+
"""
|
|
1182
|
+
status, status_resp = await self._get_query_status(sf_qid)
|
|
1183
|
+
self._cache_query_status(sf_qid, status)
|
|
1184
|
+
if self.is_an_error(status):
|
|
1185
|
+
self._process_error_query_status(sf_qid, status_resp)
|
|
1186
|
+
return status
|
|
1187
|
+
|
|
1188
|
+
@staticmethod
|
|
1189
|
+
async def setup_ocsp_privatelink(app, hostname) -> None:
|
|
1190
|
+
hostname = hostname.lower()
|
|
1191
|
+
async with SnowflakeConnection.OCSP_ENV_LOCK:
|
|
1192
|
+
ocsp_cache_server = f"http://ocsp.{hostname}/ocsp_response_cache.json"
|
|
1193
|
+
os.environ["SF_OCSP_RESPONSE_CACHE_SERVER_URL"] = ocsp_cache_server
|
|
1194
|
+
logger.debug("OCSP Cache Server is updated: %s", ocsp_cache_server)
|
|
1195
|
+
|
|
1196
|
+
async def rollback(self) -> None:
|
|
1197
|
+
"""Rolls back the current transaction."""
|
|
1198
|
+
await self.cursor().execute("ROLLBACK")
|
|
1199
|
+
|
|
1200
|
+
async def is_valid(self) -> bool:
|
|
1201
|
+
"""This function tries to answer the question: Is this connection still good for sending queries?
|
|
1202
|
+
Attempts to validate the connections both on the TCP/IP and Session levels."""
|
|
1203
|
+
logger.debug("validating connection and session")
|
|
1204
|
+
if self.is_closed():
|
|
1205
|
+
logger.debug("connection is already closed and not valid")
|
|
1206
|
+
return False
|
|
1207
|
+
|
|
1208
|
+
try:
|
|
1209
|
+
logger.debug("trying to heartbeat into the session to validate")
|
|
1210
|
+
hb_result = await self.rest._heartbeat()
|
|
1211
|
+
session_valid = hb_result.get("success")
|
|
1212
|
+
logger.debug("session still valid? %s", session_valid)
|
|
1213
|
+
return bool(session_valid)
|
|
1214
|
+
except Exception as e:
|
|
1215
|
+
logger.debug("session could not be validated due to exception: %s", e)
|
|
1216
|
+
return False
|