snowflake-connector-python 3.16.0__cp313-cp313-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.
Files changed (246) hide show
  1. snowflake/connector/__init__.py +96 -0
  2. snowflake/connector/_query_context_cache.py +276 -0
  3. snowflake/connector/_sql_util.py +48 -0
  4. snowflake/connector/_utils.py +70 -0
  5. snowflake/connector/arrow_context.py +203 -0
  6. snowflake/connector/auth/__init__.py +53 -0
  7. snowflake/connector/auth/_auth.py +586 -0
  8. snowflake/connector/auth/_http_server.py +220 -0
  9. snowflake/connector/auth/_oauth_base.py +400 -0
  10. snowflake/connector/auth/by_plugin.py +219 -0
  11. snowflake/connector/auth/default.py +36 -0
  12. snowflake/connector/auth/idtoken.py +72 -0
  13. snowflake/connector/auth/keypair.py +222 -0
  14. snowflake/connector/auth/no_auth.py +39 -0
  15. snowflake/connector/auth/oauth.py +49 -0
  16. snowflake/connector/auth/oauth_code.py +479 -0
  17. snowflake/connector/auth/oauth_credentials.py +66 -0
  18. snowflake/connector/auth/okta.py +335 -0
  19. snowflake/connector/auth/pat.py +39 -0
  20. snowflake/connector/auth/usrpwdmfa.py +69 -0
  21. snowflake/connector/auth/webbrowser.py +502 -0
  22. snowflake/connector/auth/workload_identity.py +94 -0
  23. snowflake/connector/azure_storage_client.py +276 -0
  24. snowflake/connector/backoff_policies.py +141 -0
  25. snowflake/connector/bind_upload_agent.py +89 -0
  26. snowflake/connector/cache.py +696 -0
  27. snowflake/connector/compat.py +127 -0
  28. snowflake/connector/config_manager.py +496 -0
  29. snowflake/connector/connection.py +2273 -0
  30. snowflake/connector/connection_diagnostic.py +776 -0
  31. snowflake/connector/constants.py +442 -0
  32. snowflake/connector/converter.py +785 -0
  33. snowflake/connector/converter_issue23517.py +87 -0
  34. snowflake/connector/converter_null.py +14 -0
  35. snowflake/connector/converter_snowsql.py +205 -0
  36. snowflake/connector/cursor.py +1951 -0
  37. snowflake/connector/dbapi.py +53 -0
  38. snowflake/connector/description.py +19 -0
  39. snowflake/connector/direct_file_operation_utils.py +88 -0
  40. snowflake/connector/encryption_util.py +220 -0
  41. snowflake/connector/errorcode.py +91 -0
  42. snowflake/connector/errors.py +615 -0
  43. snowflake/connector/externals_utils/__init__.py +0 -0
  44. snowflake/connector/externals_utils/externals_setup.py +27 -0
  45. snowflake/connector/feature.py +4 -0
  46. snowflake/connector/file_compression_type.py +118 -0
  47. snowflake/connector/file_lock.py +72 -0
  48. snowflake/connector/file_transfer_agent.py +1215 -0
  49. snowflake/connector/file_util.py +153 -0
  50. snowflake/connector/gcs_storage_client.py +474 -0
  51. snowflake/connector/gzip_decoder.py +85 -0
  52. snowflake/connector/local_storage_client.py +90 -0
  53. snowflake/connector/log_configuration.py +60 -0
  54. snowflake/connector/logging_utils/__init__.py +0 -0
  55. snowflake/connector/logging_utils/filters.py +72 -0
  56. snowflake/connector/nanoarrow_arrow_iterator.cp313-win_amd64.pyd +0 -0
  57. snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.cpp +60 -0
  58. snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.hpp +29 -0
  59. snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.cpp +19 -0
  60. snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.hpp +26 -0
  61. snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.cpp +21 -0
  62. snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.hpp +23 -0
  63. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.cpp +557 -0
  64. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.hpp +98 -0
  65. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.cpp +125 -0
  66. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.hpp +115 -0
  67. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.cpp +1005 -0
  68. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.hpp +127 -0
  69. snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.cpp +47 -0
  70. snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.hpp +46 -0
  71. snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.cpp +83 -0
  72. snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.hpp +35 -0
  73. snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.cpp +97 -0
  74. snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.hpp +72 -0
  75. snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.cpp +73 -0
  76. snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.hpp +28 -0
  77. snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.cpp +30 -0
  78. snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.hpp +35 -0
  79. snowflake/connector/nanoarrow_cpp/ArrowIterator/IColumnConverter.hpp +17 -0
  80. snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.cpp +23 -0
  81. snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.hpp +45 -0
  82. snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.cpp +71 -0
  83. snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.hpp +56 -0
  84. snowflake/connector/nanoarrow_cpp/ArrowIterator/LICENSE.txt +209 -0
  85. snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.cpp +75 -0
  86. snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.hpp +30 -0
  87. snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.cpp +46 -0
  88. snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.hpp +29 -0
  89. snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.cpp +8 -0
  90. snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.hpp +95 -0
  91. snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.cpp +57 -0
  92. snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.hpp +36 -0
  93. snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.cpp +34 -0
  94. snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.hpp +48 -0
  95. snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.cpp +19 -0
  96. snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.hpp +26 -0
  97. snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.cpp +36 -0
  98. snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.hpp +31 -0
  99. snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.cpp +346 -0
  100. snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.hpp +145 -0
  101. snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/macros.hpp +14 -0
  102. snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.cpp +65 -0
  103. snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.hpp +68 -0
  104. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_accessors.h +101 -0
  105. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_alloc.h +127 -0
  106. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_assert.h +45 -0
  107. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_builder.h +1908 -0
  108. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_emitter.h +215 -0
  109. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_endian.h +125 -0
  110. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_epilogue.h +7 -0
  111. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_flatbuffers.h +55 -0
  112. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_identifier.h +148 -0
  113. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_iov.h +31 -0
  114. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_prologue.h +8 -0
  115. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_refmap.h +144 -0
  116. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_rtconfig.h +162 -0
  117. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_types.h +97 -0
  118. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_verifier.h +239 -0
  119. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/flatcc_portable.h +14 -0
  120. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/paligned_alloc.h +210 -0
  121. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pattributes.h +84 -0
  122. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic.h +84 -0
  123. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_pop.h +20 -0
  124. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_push.h +51 -0
  125. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian.h +206 -0
  126. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian_detect.h +118 -0
  127. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinline.h +19 -0
  128. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinttypes.h +52 -0
  129. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable.h +2 -0
  130. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable_basic.h +25 -0
  131. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstatic_assert.h +67 -0
  132. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdalign.h +162 -0
  133. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdint.h +898 -0
  134. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/punaligned.h +190 -0
  135. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pversion.h +6 -0
  136. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pwarnings.h +52 -0
  137. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc.c +3204 -0
  138. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.c +3217 -0
  139. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.h +3618 -0
  140. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.hpp +379 -0
  141. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_arrow_iterator.pyx +256 -0
  142. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.c +512 -0
  143. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.h +350 -0
  144. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.c +33273 -0
  145. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.h +397 -0
  146. snowflake/connector/nanoarrow_cpp/Logging/logging.cpp +100 -0
  147. snowflake/connector/nanoarrow_cpp/Logging/logging.hpp +49 -0
  148. snowflake/connector/network.py +1297 -0
  149. snowflake/connector/ocsp_asn1crypto.py +447 -0
  150. snowflake/connector/ocsp_snowflake.py +1883 -0
  151. snowflake/connector/options.py +131 -0
  152. snowflake/connector/pandas_tools.py +732 -0
  153. snowflake/connector/proxy.py +43 -0
  154. snowflake/connector/py.typed +0 -0
  155. snowflake/connector/result_batch.py +786 -0
  156. snowflake/connector/result_set.py +319 -0
  157. snowflake/connector/s3_storage_client.py +605 -0
  158. snowflake/connector/secret_detector.py +181 -0
  159. snowflake/connector/sf_dirs.py +64 -0
  160. snowflake/connector/sfbinaryformat.py +35 -0
  161. snowflake/connector/sfdatetime.py +345 -0
  162. snowflake/connector/snow_logging.py +144 -0
  163. snowflake/connector/sqlstate.py +9 -0
  164. snowflake/connector/ssd_internal_keys.py +30 -0
  165. snowflake/connector/ssl_wrap_socket.py +134 -0
  166. snowflake/connector/storage_client.py +478 -0
  167. snowflake/connector/telemetry.py +249 -0
  168. snowflake/connector/telemetry_oob.py +543 -0
  169. snowflake/connector/test_util.py +30 -0
  170. snowflake/connector/time_util.py +159 -0
  171. snowflake/connector/token_cache.py +402 -0
  172. snowflake/connector/tool/__init__.py +0 -0
  173. snowflake/connector/tool/dump_certs.py +57 -0
  174. snowflake/connector/tool/dump_ocsp_response.py +139 -0
  175. snowflake/connector/tool/dump_ocsp_response_cache.py +194 -0
  176. snowflake/connector/tool/probe_connection.py +69 -0
  177. snowflake/connector/url_util.py +49 -0
  178. snowflake/connector/util_text.py +301 -0
  179. snowflake/connector/vendored/__init__.py +3 -0
  180. snowflake/connector/vendored/requests/LICENSE +175 -0
  181. snowflake/connector/vendored/requests/__init__.py +169 -0
  182. snowflake/connector/vendored/requests/__version__.py +14 -0
  183. snowflake/connector/vendored/requests/_internal_utils.py +50 -0
  184. snowflake/connector/vendored/requests/adapters.py +537 -0
  185. snowflake/connector/vendored/requests/api.py +157 -0
  186. snowflake/connector/vendored/requests/auth.py +315 -0
  187. snowflake/connector/vendored/requests/certs.py +17 -0
  188. snowflake/connector/vendored/requests/compat.py +79 -0
  189. snowflake/connector/vendored/requests/cookies.py +561 -0
  190. snowflake/connector/vendored/requests/exceptions.py +140 -0
  191. snowflake/connector/vendored/requests/help.py +134 -0
  192. snowflake/connector/vendored/requests/hooks.py +33 -0
  193. snowflake/connector/vendored/requests/models.py +1033 -0
  194. snowflake/connector/vendored/requests/sessions.py +833 -0
  195. snowflake/connector/vendored/requests/status_codes.py +128 -0
  196. snowflake/connector/vendored/requests/structures.py +99 -0
  197. snowflake/connector/vendored/requests/utils.py +1093 -0
  198. snowflake/connector/vendored/urllib3/LICENSE.txt +21 -0
  199. snowflake/connector/vendored/urllib3/__init__.py +85 -0
  200. snowflake/connector/vendored/urllib3/_collections.py +355 -0
  201. snowflake/connector/vendored/urllib3/_version.py +2 -0
  202. snowflake/connector/vendored/urllib3/connection.py +572 -0
  203. snowflake/connector/vendored/urllib3/connectionpool.py +1137 -0
  204. snowflake/connector/vendored/urllib3/contrib/__init__.py +0 -0
  205. snowflake/connector/vendored/urllib3/contrib/_appengine_environ.py +36 -0
  206. snowflake/connector/vendored/urllib3/contrib/_securetransport/__init__.py +0 -0
  207. snowflake/connector/vendored/urllib3/contrib/_securetransport/bindings.py +519 -0
  208. snowflake/connector/vendored/urllib3/contrib/_securetransport/low_level.py +397 -0
  209. snowflake/connector/vendored/urllib3/contrib/appengine.py +314 -0
  210. snowflake/connector/vendored/urllib3/contrib/ntlmpool.py +130 -0
  211. snowflake/connector/vendored/urllib3/contrib/pyopenssl.py +509 -0
  212. snowflake/connector/vendored/urllib3/contrib/securetransport.py +920 -0
  213. snowflake/connector/vendored/urllib3/contrib/socks.py +216 -0
  214. snowflake/connector/vendored/urllib3/exceptions.py +323 -0
  215. snowflake/connector/vendored/urllib3/fields.py +274 -0
  216. snowflake/connector/vendored/urllib3/filepost.py +98 -0
  217. snowflake/connector/vendored/urllib3/packages/__init__.py +0 -0
  218. snowflake/connector/vendored/urllib3/packages/backports/__init__.py +0 -0
  219. snowflake/connector/vendored/urllib3/packages/backports/makefile.py +51 -0
  220. snowflake/connector/vendored/urllib3/packages/backports/weakref_finalize.py +155 -0
  221. snowflake/connector/vendored/urllib3/packages/six.py +1076 -0
  222. snowflake/connector/vendored/urllib3/poolmanager.py +540 -0
  223. snowflake/connector/vendored/urllib3/request.py +191 -0
  224. snowflake/connector/vendored/urllib3/response.py +885 -0
  225. snowflake/connector/vendored/urllib3/util/__init__.py +49 -0
  226. snowflake/connector/vendored/urllib3/util/connection.py +156 -0
  227. snowflake/connector/vendored/urllib3/util/proxy.py +57 -0
  228. snowflake/connector/vendored/urllib3/util/queue.py +22 -0
  229. snowflake/connector/vendored/urllib3/util/request.py +146 -0
  230. snowflake/connector/vendored/urllib3/util/response.py +107 -0
  231. snowflake/connector/vendored/urllib3/util/retry.py +620 -0
  232. snowflake/connector/vendored/urllib3/util/ssl_.py +495 -0
  233. snowflake/connector/vendored/urllib3/util/ssl_match_hostname.py +159 -0
  234. snowflake/connector/vendored/urllib3/util/ssltransport.py +221 -0
  235. snowflake/connector/vendored/urllib3/util/timeout.py +271 -0
  236. snowflake/connector/vendored/urllib3/util/url.py +435 -0
  237. snowflake/connector/vendored/urllib3/util/wait.py +152 -0
  238. snowflake/connector/version.py +3 -0
  239. snowflake/connector/wif_util.py +407 -0
  240. snowflake_connector_python-3.16.0.dist-info/METADATA +1475 -0
  241. snowflake_connector_python-3.16.0.dist-info/RECORD +246 -0
  242. snowflake_connector_python-3.16.0.dist-info/WHEEL +5 -0
  243. snowflake_connector_python-3.16.0.dist-info/entry_points.txt +4 -0
  244. snowflake_connector_python-3.16.0.dist-info/licenses/LICENSE.txt +202 -0
  245. snowflake_connector_python-3.16.0.dist-info/licenses/NOTICE +8 -0
  246. snowflake_connector_python-3.16.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2273 @@
1
+ #!/usr/bin/env python
2
+ from __future__ import annotations
3
+
4
+ import atexit
5
+ import logging
6
+ import os
7
+ import pathlib
8
+ import re
9
+ import sys
10
+ import traceback
11
+ import uuid
12
+ import warnings
13
+ import weakref
14
+ from concurrent.futures import as_completed
15
+ from concurrent.futures.thread import ThreadPoolExecutor
16
+ from contextlib import suppress
17
+ from difflib import get_close_matches
18
+ from functools import partial
19
+ from io import StringIO
20
+ from logging import getLogger
21
+ from threading import Lock
22
+ from types import TracebackType
23
+ from typing import Any, Callable, Generator, Iterable, Iterator, NamedTuple, Sequence
24
+ from uuid import UUID
25
+
26
+ from cryptography.hazmat.backends import default_backend
27
+ from cryptography.hazmat.primitives import serialization
28
+ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
29
+
30
+ from . import errors, proxy
31
+ from ._query_context_cache import QueryContextCache
32
+ from .auth import (
33
+ FIRST_PARTY_AUTHENTICATORS,
34
+ Auth,
35
+ AuthByDefault,
36
+ AuthByKeyPair,
37
+ AuthByOAuth,
38
+ AuthByOauthCode,
39
+ AuthByOauthCredentials,
40
+ AuthByOkta,
41
+ AuthByPAT,
42
+ AuthByPlugin,
43
+ AuthByUsrPwdMfa,
44
+ AuthByWebBrowser,
45
+ AuthByWorkloadIdentity,
46
+ AuthNoAuth,
47
+ )
48
+ from .auth.idtoken import AuthByIdToken
49
+ from .backoff_policies import exponential_backoff
50
+ from .bind_upload_agent import BindUploadError
51
+ from .compat import IS_LINUX, IS_WINDOWS, quote, urlencode
52
+ from .config_manager import CONFIG_MANAGER, _get_default_connection_params
53
+ from .connection_diagnostic import ConnectionDiagnostic
54
+ from .constants import (
55
+ _CONNECTIVITY_ERR_MSG,
56
+ _DOMAIN_NAME_MAP,
57
+ _OAUTH_DEFAULT_SCOPE,
58
+ ENV_VAR_EXPERIMENTAL_AUTHENTICATION,
59
+ ENV_VAR_PARTNER,
60
+ PARAMETER_AUTOCOMMIT,
61
+ PARAMETER_CLIENT_PREFETCH_THREADS,
62
+ PARAMETER_CLIENT_REQUEST_MFA_TOKEN,
63
+ PARAMETER_CLIENT_SESSION_KEEP_ALIVE,
64
+ PARAMETER_CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY,
65
+ PARAMETER_CLIENT_STORE_TEMPORARY_CREDENTIAL,
66
+ PARAMETER_CLIENT_TELEMETRY_ENABLED,
67
+ PARAMETER_CLIENT_VALIDATE_DEFAULT_PARAMETERS,
68
+ PARAMETER_ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1,
69
+ PARAMETER_QUERY_CONTEXT_CACHE_SIZE,
70
+ PARAMETER_SERVICE_NAME,
71
+ PARAMETER_TIMEZONE,
72
+ OCSPMode,
73
+ QueryStatus,
74
+ )
75
+ from .converter import SnowflakeConverter
76
+ from .cursor import LOG_MAX_QUERY_LENGTH, SnowflakeCursor
77
+ from .description import (
78
+ CLIENT_NAME,
79
+ CLIENT_VERSION,
80
+ PLATFORM,
81
+ PYTHON_VERSION,
82
+ SNOWFLAKE_CONNECTOR_VERSION,
83
+ )
84
+ from .direct_file_operation_utils import FileOperationParser, StreamDownloader
85
+ from .errorcode import (
86
+ ER_CONNECTION_IS_CLOSED,
87
+ ER_EXPERIMENTAL_AUTHENTICATION_NOT_SUPPORTED,
88
+ ER_FAILED_PROCESSING_PYFORMAT,
89
+ ER_FAILED_PROCESSING_QMARK,
90
+ ER_FAILED_TO_CONNECT_TO_DB,
91
+ ER_INVALID_BACKOFF_POLICY,
92
+ ER_INVALID_VALUE,
93
+ ER_INVALID_WIF_SETTINGS,
94
+ ER_NO_ACCOUNT_NAME,
95
+ ER_NO_NUMPY,
96
+ ER_NO_PASSWORD,
97
+ ER_NO_USER,
98
+ ER_NOT_IMPLICITY_SNOWFLAKE_DATATYPE,
99
+ )
100
+ from .errors import DatabaseError, Error, OperationalError, ProgrammingError
101
+ from .log_configuration import EasyLoggingConfigPython
102
+ from .network import (
103
+ DEFAULT_AUTHENTICATOR,
104
+ EXTERNAL_BROWSER_AUTHENTICATOR,
105
+ KEY_PAIR_AUTHENTICATOR,
106
+ NO_AUTH_AUTHENTICATOR,
107
+ OAUTH_AUTHENTICATOR,
108
+ OAUTH_AUTHORIZATION_CODE,
109
+ OAUTH_CLIENT_CREDENTIALS,
110
+ PAT_WITH_EXTERNAL_SESSION,
111
+ PROGRAMMATIC_ACCESS_TOKEN,
112
+ REQUEST_ID,
113
+ USR_PWD_MFA_AUTHENTICATOR,
114
+ WORKLOAD_IDENTITY_AUTHENTICATOR,
115
+ ReauthenticationRequest,
116
+ SnowflakeRestful,
117
+ )
118
+ from .sqlstate import SQLSTATE_CONNECTION_NOT_EXISTS, SQLSTATE_FEATURE_NOT_SUPPORTED
119
+ from .telemetry import TelemetryClient, TelemetryData, TelemetryField
120
+ from .time_util import HeartBeatTimer, get_time_millis
121
+ from .url_util import extract_top_level_domain_from_hostname
122
+ from .util_text import construct_hostname, parse_account, split_statements
123
+ from .wif_util import AttestationProvider
124
+
125
+ DEFAULT_CLIENT_PREFETCH_THREADS = 4
126
+ MAX_CLIENT_PREFETCH_THREADS = 10
127
+ MAX_CLIENT_FETCH_THREADS = 1024
128
+ DEFAULT_BACKOFF_POLICY = exponential_backoff()
129
+
130
+
131
+ def DefaultConverterClass() -> type:
132
+ if IS_WINDOWS:
133
+ from .converter_issue23517 import SnowflakeConverterIssue23517
134
+
135
+ return SnowflakeConverterIssue23517
136
+ else:
137
+ from .converter import SnowflakeConverter
138
+
139
+ return SnowflakeConverter
140
+
141
+
142
+ def _get_private_bytes_from_file(
143
+ private_key_file: str | bytes | os.PathLike[str] | os.PathLike[bytes],
144
+ private_key_file_pwd: bytes | str | None = None,
145
+ ) -> bytes:
146
+ if private_key_file_pwd is not None and isinstance(private_key_file_pwd, str):
147
+ private_key_file_pwd = private_key_file_pwd.encode("utf-8")
148
+ with open(private_key_file, "rb") as key:
149
+ private_key = serialization.load_pem_private_key(
150
+ key.read(),
151
+ password=private_key_file_pwd,
152
+ backend=default_backend(),
153
+ )
154
+
155
+ pkb = private_key.private_bytes(
156
+ encoding=serialization.Encoding.DER,
157
+ format=serialization.PrivateFormat.PKCS8,
158
+ encryption_algorithm=serialization.NoEncryption(),
159
+ )
160
+
161
+ return pkb
162
+
163
+
164
+ SUPPORTED_PARAMSTYLES = {
165
+ "qmark",
166
+ "numeric",
167
+ "format",
168
+ "pyformat",
169
+ }
170
+ # Default configs, tuple of default variable and accepted types
171
+ DEFAULT_CONFIGURATION: dict[str, tuple[Any, type | tuple[type, ...]]] = {
172
+ "dsn": (None, (type(None), str)), # standard
173
+ "user": ("", str), # standard
174
+ "password": ("", str), # standard
175
+ "host": ("127.0.0.1", str), # standard
176
+ "port": (443, (int, str)), # standard
177
+ "database": (None, (type(None), str)), # standard
178
+ "proxy_host": (None, (type(None), str)), # snowflake
179
+ "proxy_port": (None, (type(None), str)), # snowflake
180
+ "proxy_user": (None, (type(None), str)), # snowflake
181
+ "proxy_password": (None, (type(None), str)), # snowflake
182
+ "protocol": ("https", str), # snowflake
183
+ "warehouse": (None, (type(None), str)), # snowflake
184
+ "region": (None, (type(None), str)), # snowflake
185
+ "account": (None, (type(None), str)), # snowflake
186
+ "schema": (None, (type(None), str)), # snowflake
187
+ "role": (None, (type(None), str)), # snowflake
188
+ "session_id": (None, (type(None), str)), # snowflake
189
+ "login_timeout": (None, (type(None), int)), # login timeout
190
+ "network_timeout": (
191
+ None,
192
+ (type(None), int),
193
+ ), # network timeout (infinite by default)
194
+ "socket_timeout": (None, (type(None), int)),
195
+ "external_browser_timeout": (120, int),
196
+ "backoff_policy": (DEFAULT_BACKOFF_POLICY, Callable),
197
+ "passcode_in_password": (False, bool), # Snowflake MFA
198
+ "passcode": (None, (type(None), str)), # Snowflake MFA
199
+ "private_key": (None, (type(None), bytes, str, RSAPrivateKey)),
200
+ "private_key_file": (None, (type(None), str)),
201
+ "private_key_file_pwd": (None, (type(None), str, bytes)),
202
+ "token": (None, (type(None), str)), # OAuth/JWT/PAT/OIDC Token
203
+ "token_file_path": (
204
+ None,
205
+ (type(None), str, bytes),
206
+ ), # OAuth/JWT/PAT/OIDC Token file path
207
+ "authenticator": (DEFAULT_AUTHENTICATOR, (type(None), str)),
208
+ "workload_identity_provider": (None, (type(None), AttestationProvider)),
209
+ "workload_identity_entra_resource": (None, (type(None), str)),
210
+ "mfa_callback": (None, (type(None), Callable)),
211
+ "password_callback": (None, (type(None), Callable)),
212
+ "auth_class": (None, (type(None), AuthByPlugin)),
213
+ "application": (CLIENT_NAME, (type(None), str)),
214
+ # internal_application_name/version is used to tell the server the type of client connecting to
215
+ # Snowflake. There are some functionalities (e.g., MFA, Arrow result format) that
216
+ # Snowflake server doesn't support for new client types, which requires developers to
217
+ # add the new client type to the server to support these features.
218
+ "internal_application_name": (CLIENT_NAME, (type(None), str)),
219
+ "internal_application_version": (CLIENT_VERSION, (type(None), str)),
220
+ "disable_ocsp_checks": (False, bool),
221
+ "ocsp_fail_open": (True, bool), # fail open on ocsp issues, default true
222
+ "inject_client_pause": (0, int), # snowflake internal
223
+ "session_parameters": (None, (type(None), dict)), # snowflake session parameters
224
+ "autocommit": (None, (type(None), bool)), # snowflake
225
+ "client_session_keep_alive": (None, (type(None), bool)), # snowflake
226
+ "client_session_keep_alive_heartbeat_frequency": (
227
+ None,
228
+ (type(None), int),
229
+ ), # snowflake
230
+ "client_prefetch_threads": (4, int), # snowflake
231
+ "client_fetch_threads": (None, (type(None), int)),
232
+ "client_fetch_use_mp": (False, bool),
233
+ "numpy": (False, bool), # snowflake
234
+ "ocsp_response_cache_filename": (None, (type(None), str)), # snowflake internal
235
+ "converter_class": (DefaultConverterClass(), SnowflakeConverter),
236
+ "validate_default_parameters": (False, bool), # snowflake
237
+ "probe_connection": (False, bool), # snowflake
238
+ "paramstyle": (None, (type(None), str)), # standard/snowflake
239
+ "timezone": (None, (type(None), str)), # snowflake
240
+ "consent_cache_id_token": (True, bool), # snowflake
241
+ "service_name": (None, (type(None), str)), # snowflake
242
+ "support_negative_year": (True, bool), # snowflake
243
+ "log_max_query_length": (LOG_MAX_QUERY_LENGTH, int), # snowflake
244
+ "disable_request_pooling": (False, bool), # snowflake
245
+ # enable temporary credential file for Linux, default false. Mac/Win will overlook this
246
+ "client_store_temporary_credential": (False, bool),
247
+ "client_request_mfa_token": (False, bool),
248
+ "use_openssl_only": (
249
+ True,
250
+ bool,
251
+ ), # ignored - python only crypto modules are no longer used
252
+ # whether to convert Arrow number values to decimal instead of doubles
253
+ "arrow_number_to_decimal": (False, bool),
254
+ "enable_stage_s3_privatelink_for_us_east_1": (
255
+ False,
256
+ bool,
257
+ ), # only use regional url when the param is set
258
+ # Allows cursors to be re-iterable
259
+ "reuse_results": (False, bool),
260
+ # parameter protecting behavior change of SNOW-501058
261
+ "interpolate_empty_sequences": (False, bool),
262
+ "enable_connection_diag": (False, bool), # Generate SnowCD like report
263
+ "connection_diag_log_path": (
264
+ None,
265
+ (type(None), str),
266
+ ), # Path to connection diag report
267
+ "connection_diag_whitelist_path": (
268
+ None,
269
+ (type(None), str),
270
+ ), # Path to connection diag whitelist json - Deprecated remove in future
271
+ "connection_diag_allowlist_path": (
272
+ None,
273
+ (type(None), str),
274
+ ), # Path to connection diag allowlist json
275
+ "log_imported_packages_in_telemetry": (
276
+ True,
277
+ bool,
278
+ ), # Whether to log imported packages in telemetry
279
+ "disable_query_context_cache": (
280
+ False,
281
+ bool,
282
+ ), # Disable query context cache
283
+ "json_result_force_utf8_decoding": (
284
+ False,
285
+ bool,
286
+ ), # Whether to force the JSON content to be decoded in utf-8, it is only effective when result format is JSON
287
+ "server_session_keep_alive": (
288
+ False,
289
+ bool,
290
+ ), # Whether to keep session alive after connector shuts down
291
+ "enable_retry_reason_in_query_response": (
292
+ True,
293
+ bool,
294
+ ), # Enable sending retryReason in response header for query-requests
295
+ "session_token": (
296
+ None,
297
+ (type(None), str),
298
+ ), # session token from another connection, to be provided together with master token
299
+ "master_token": (
300
+ None,
301
+ (type(None), str),
302
+ ), # master token from another connection, to be provided together with session token
303
+ "master_validity_in_seconds": (
304
+ None,
305
+ (type(None), int),
306
+ ), # master token validity in seconds
307
+ "disable_console_login": (
308
+ True,
309
+ bool,
310
+ ), # Disable console login and fall back to getting SSO URL from GS
311
+ "debug_arrow_chunk": (
312
+ False,
313
+ bool,
314
+ ), # log raw arrow chunk for debugging purpuse in case there is malformed arrow data
315
+ "disable_saml_url_check": (
316
+ False,
317
+ bool,
318
+ ), # disable saml url check in okta authentication
319
+ "iobound_tpe_limit": (
320
+ None,
321
+ (type(None), int),
322
+ ), # SNOW-1817982: limit iobound TPE sizes when executing PUT/GET
323
+ "oauth_client_id": (
324
+ None,
325
+ (type(None), str),
326
+ # SNOW-1825621: OAUTH implementation
327
+ ),
328
+ "oauth_client_secret": (
329
+ None,
330
+ (type(None), str),
331
+ # SNOW-1825621: OAUTH implementation
332
+ ),
333
+ "oauth_authorization_url": (
334
+ "https://{host}:{port}/oauth/authorize",
335
+ str,
336
+ # SNOW-1825621: OAUTH implementation
337
+ ),
338
+ "oauth_token_request_url": (
339
+ "https://{host}:{port}/oauth/token-request",
340
+ str,
341
+ # SNOW-1825621: OAUTH implementation
342
+ ),
343
+ "oauth_redirect_uri": ("http://127.0.0.1", str),
344
+ "oauth_scope": (
345
+ "",
346
+ str,
347
+ # SNOW-1825621: OAUTH implementation
348
+ ),
349
+ "oauth_disable_pkce": (
350
+ False,
351
+ bool,
352
+ # SNOW-1825621: OAUTH PKCE
353
+ ),
354
+ "oauth_enable_refresh_tokens": (
355
+ False,
356
+ bool,
357
+ ),
358
+ "oauth_enable_single_use_refresh_tokens": (
359
+ False,
360
+ bool,
361
+ # Client-side opt-in to single-use refresh tokens.
362
+ ),
363
+ "check_arrow_conversion_error_on_every_column": (
364
+ True,
365
+ bool,
366
+ ), # SNOW-XXXXX: remove the check_arrow_conversion_error_on_every_column flag
367
+ "external_session_id": (
368
+ None,
369
+ str,
370
+ # SNOW-2096721: External (Spark) session ID
371
+ ),
372
+ }
373
+
374
+ APPLICATION_RE = re.compile(r"[\w\d_]+")
375
+
376
+ # adding the exception class to Connection class
377
+ for m in [method for method in dir(errors) if callable(getattr(errors, method))]:
378
+ setattr(sys.modules[__name__], m, getattr(errors, m))
379
+
380
+ logger = getLogger(__name__)
381
+
382
+
383
+ class TypeAndBinding(NamedTuple):
384
+ """Stores the type name and the Snowflake binding."""
385
+
386
+ type: str
387
+ binding: str | None
388
+
389
+
390
+ class SnowflakeConnection:
391
+ """Implementation of the connection object for the Snowflake Database.
392
+
393
+ Use connect(..) to get the object.
394
+
395
+ Attributes:
396
+ insecure_mode (deprecated): Whether or not the connection is in OCSP disabled mode. It means that the connection
397
+ validates the TLS certificate but doesn't check revocation status with OCSP provider.
398
+ disable_ocsp_checks: Whether or not the connection is in OCSP disabled mode. It means that the connection
399
+ validates the TLS certificate but doesn't check revocation status with OCSP provider.
400
+ ocsp_fail_open: Whether or not the connection is in fail open mode. Fail open mode decides if TLS certificates
401
+ continue to be validated. Revoked certificates are blocked. Any other exceptions are disregarded.
402
+ session_id: The session ID of the connection.
403
+ user: The user name used in the connection.
404
+ host: The host name the connection attempts to connect to.
405
+ port: The port to communicate with on the host.
406
+ region: Region name if not the default Snowflake Database deployment.
407
+ proxy_host: The hostname used proxy server.
408
+ proxy_port: Port on proxy server to communicate with.
409
+ proxy_user: User name to login with on the proxy sever.
410
+ proxy_password: Password to be used to authenticate with proxy server.
411
+ account: Account name to be used to authenticate with Snowflake.
412
+ database: Database to use on Snowflake.
413
+ schema: Schema in use on Snowflake.
414
+ warehouse: Warehouse to be used on Snowflake.
415
+ role: Role in use on Snowflake.
416
+ login_timeout: Login timeout in seconds. Login requests will not be retried after this timeout expires.
417
+ Note that the login attempt may still take more than login_timeout seconds as an ongoing login request
418
+ cannot be canceled even upon login timeout expiry. The login timeout only prevents further retries.
419
+ If not specified, login_timeout is set to `snowflake.connector.auth.by_plugin.DEFAULT_AUTH_CLASS_TIMEOUT`.
420
+ Note that the number of retries on login requests is still limited by
421
+ `snowflake.connector.auth.by_plugin.DEFAULT_MAX_CON_RETRY_ATTEMPTS`.
422
+ network_timeout: Network timeout in seconds. Network requests besides login requests will not be retried
423
+ after this timeout expires. Overriden in cursor query execution if timeout is passed to cursor.execute.
424
+ Note that an operation may still take more than network_timeout seconds for the same reason as above.
425
+ If not specified, network_timeout is infinite.
426
+ socket_timeout: Socket timeout in seconds. Sets both socket connect and read timeout.
427
+ backoff_policy: Backoff policy to use for login and network requests. Must be a callable generator function.
428
+ Standard linear and exponential backoff implementations are included in `snowflake.connector.backoff_policies`
429
+ See the backoff_policies module for details and implementation examples.
430
+ client_session_keep_alive_heartbeat_frequency: Heartbeat frequency to keep connection alive in seconds.
431
+ client_prefetch_threads: Number of threads to download the result set.
432
+ client_fetch_threads: Number of threads (or processes) to fetch staged query results.
433
+ If not specified, reuses client_prefetch_threads value.
434
+ client_fetch_use_mp: Enables multiprocessing for fetching query results in parallel.
435
+ rest: Snowflake REST API object. Internal use only. Maybe removed in a later release.
436
+ application: Application name to communicate with Snowflake as. By default, this is "PythonConnector".
437
+ errorhandler: Handler used with errors. By default, an exception will be raised on error.
438
+ converter_class: Handler used to convert data to Python native objects.
439
+ validate_default_parameters: Validate database, schema, role and warehouse used on Snowflake.
440
+ is_pyformat: Whether the current argument binding is pyformat or format.
441
+ consent_cache_id_token: Consented cache ID token.
442
+ enable_stage_s3_privatelink_for_us_east_1: when true, clients use regional s3 url to upload files.
443
+ enable_connection_diag: when true, clients will generate a connectivity diagnostic report.
444
+ connection_diag_log_path: path to location to create diag report with enable_connection_diag.
445
+ connection_diag_whitelist_path: path to a whitelist.json file to test with enable_connection_diag - deprecated remove in future
446
+ connection_diag_allowlist_path: path to a allowlist.json file to test with enable_connection_diag.
447
+ json_result_force_utf8_decoding: When true, json result will be decoded in utf-8,
448
+ when false, the encoding of the content is auto-detected. Default value is false.
449
+ This parameter is only effective when the result format is JSON.
450
+ server_session_keep_alive: When true, the connector does not destroy the session on the Snowflake server side
451
+ before the connector shuts down. Default value is false.
452
+ token_file_path: The file path of the token file. If both token and token_file_path are provided, the token in token_file_path will be used.
453
+ unsafe_file_write: When true, files downloaded by GET will be saved with 644 permissions. Otherwise, files will be saved with safe - owner-only permissions: 600.
454
+ check_arrow_conversion_error_on_every_column: When true, the error check after the conversion from arrow to python types will happen for every column in the row. This is a new behaviour which fixes the bug that caused the type errors to trigger silently when occurring at any place other than last column in a row. To revert the previous (faulty) behaviour, please set this flag to false.
455
+ """
456
+
457
+ OCSP_ENV_LOCK = Lock()
458
+
459
+ def __init__(
460
+ self,
461
+ connection_name: str | None = None,
462
+ connections_file_path: pathlib.Path | None = None,
463
+ **kwargs,
464
+ ) -> None:
465
+ """Create a new SnowflakeConnection.
466
+
467
+ Connections can be loaded from the TOML file located at
468
+ snowflake.connector.constants.CONNECTIONS_FILE.
469
+
470
+ When connection_name is supplied we will first load that connection
471
+ and then override any other values supplied.
472
+
473
+ When no arguments are given (other than connection_file_path) the
474
+ default connection will be loaded first. Note that no overwriting is
475
+ supported in this case.
476
+
477
+ If overwriting values from the default connection is desirable, supply
478
+ the name explicitly.
479
+ """
480
+ # initiate easy logging during every connection
481
+ easy_logging = EasyLoggingConfigPython()
482
+ easy_logging.create_log()
483
+ self._lock_sequence_counter = Lock()
484
+ self.sequence_counter = 0
485
+ self._errorhandler = Error.default_errorhandler
486
+ self._lock_converter = Lock()
487
+ self.messages = []
488
+ self._async_sfqids: dict[str, None] = {}
489
+ self._done_async_sfqids: dict[str, None] = {}
490
+ self._client_param_telemetry_enabled = True
491
+ self._server_param_telemetry_enabled = False
492
+ self._session_parameters: dict[str, str | int | bool] = {}
493
+ logger.info(
494
+ "Snowflake Connector for Python Version: %s, "
495
+ "Python Version: %s, Platform: %s",
496
+ SNOWFLAKE_CONNECTOR_VERSION,
497
+ PYTHON_VERSION,
498
+ PLATFORM,
499
+ )
500
+
501
+ self._rest = None
502
+ for name, (value, _) in DEFAULT_CONFIGURATION.items():
503
+ setattr(self, f"_{name}", value)
504
+
505
+ self.heartbeat_thread = None
506
+ is_kwargs_empty = not kwargs
507
+
508
+ if "application" not in kwargs:
509
+ app = self._detect_application()
510
+ if app:
511
+ kwargs["application"] = app
512
+
513
+ if "insecure_mode" in kwargs:
514
+ warn_message = "The 'insecure_mode' connection property is deprecated. Please use 'disable_ocsp_checks' instead"
515
+ warnings.warn(
516
+ warn_message,
517
+ DeprecationWarning,
518
+ stacklevel=2,
519
+ )
520
+
521
+ if (
522
+ "disable_ocsp_checks" in kwargs
523
+ and kwargs["disable_ocsp_checks"] != kwargs["insecure_mode"]
524
+ ):
525
+ logger.warning(
526
+ "The values for 'disable_ocsp_checks' and 'insecure_mode' differ. "
527
+ "Using the value of 'disable_ocsp_checks."
528
+ )
529
+ else:
530
+ self._disable_ocsp_checks = kwargs["insecure_mode"]
531
+
532
+ self.converter = None
533
+ self.query_context_cache: QueryContextCache | None = None
534
+ self.query_context_cache_size = 5
535
+ if connections_file_path is not None:
536
+ # Change config file path and force update cache
537
+ for i, s in enumerate(CONFIG_MANAGER._slices):
538
+ if s.section == "connections":
539
+ CONFIG_MANAGER._slices[i] = s._replace(path=connections_file_path)
540
+ CONFIG_MANAGER.read_config()
541
+ break
542
+ if connection_name is not None:
543
+ connections = CONFIG_MANAGER["connections"]
544
+ if connection_name not in connections:
545
+ raise Error(
546
+ f"Invalid connection_name '{connection_name}',"
547
+ f" known ones are {list(connections.keys())}"
548
+ )
549
+ kwargs = {**connections[connection_name], **kwargs}
550
+ elif is_kwargs_empty:
551
+ # connection_name is None and kwargs was empty when called
552
+ kwargs = _get_default_connection_params()
553
+ self.__set_error_attributes()
554
+ self.connect(**kwargs)
555
+ self._telemetry = TelemetryClient(self._rest)
556
+ self.expired = False
557
+
558
+ # get the imported modules from sys.modules
559
+ self._log_telemetry_imported_packages()
560
+ # check SNOW-1218851 for long term improvement plan to refactor ocsp code
561
+ atexit.register(self._close_at_exit)
562
+
563
+ # Set up the file operation parser and stream downloader.
564
+ self._file_operation_parser = FileOperationParser(self)
565
+ self._stream_downloader = StreamDownloader(self)
566
+
567
+ # Deprecated
568
+ @property
569
+ def insecure_mode(self) -> bool:
570
+ return self._disable_ocsp_checks
571
+
572
+ @property
573
+ def disable_ocsp_checks(self) -> bool:
574
+ return self._disable_ocsp_checks
575
+
576
+ @property
577
+ def ocsp_fail_open(self) -> bool:
578
+ return self._ocsp_fail_open
579
+
580
+ def _ocsp_mode(self) -> OCSPMode:
581
+ """OCSP mode. DISABLE_OCSP_CHECKS, FAIL_OPEN or FAIL_CLOSED."""
582
+ if self.disable_ocsp_checks:
583
+ return OCSPMode.DISABLE_OCSP_CHECKS
584
+ elif self.ocsp_fail_open:
585
+ return OCSPMode.FAIL_OPEN
586
+ else:
587
+ return OCSPMode.FAIL_CLOSED
588
+
589
+ @property
590
+ def session_id(self) -> int:
591
+ return self._session_id
592
+
593
+ @property
594
+ def user(self) -> str:
595
+ return self._user
596
+
597
+ @property
598
+ def host(self) -> str:
599
+ return self._host
600
+
601
+ @property
602
+ def port(self) -> int:
603
+ return int(self._port)
604
+
605
+ @property
606
+ def region(self) -> str | None:
607
+ warnings.warn(
608
+ "Region has been deprecated and will be removed in the near future",
609
+ PendingDeprecationWarning,
610
+ # Raise warning from where this property was called from
611
+ stacklevel=2,
612
+ )
613
+ return self._region
614
+
615
+ @property
616
+ def proxy_host(self) -> str | None:
617
+ return self._proxy_host
618
+
619
+ @property
620
+ def proxy_port(self) -> str | None:
621
+ return self._proxy_port
622
+
623
+ @property
624
+ def proxy_user(self) -> str | None:
625
+ return self._proxy_user
626
+
627
+ @property
628
+ def proxy_password(self) -> str | None:
629
+ return self._proxy_password
630
+
631
+ @property
632
+ def account(self) -> str:
633
+ return self._account
634
+
635
+ @property
636
+ def database(self) -> str | None:
637
+ return self._database
638
+
639
+ @property
640
+ def schema(self) -> str | None:
641
+ return self._schema
642
+
643
+ @property
644
+ def warehouse(self) -> str | None:
645
+ return self._warehouse
646
+
647
+ @property
648
+ def role(self) -> str | None:
649
+ return self._role
650
+
651
+ @property
652
+ def login_timeout(self) -> int | None:
653
+ return int(self._login_timeout) if self._login_timeout is not None else None
654
+
655
+ @property
656
+ def network_timeout(self) -> int | None:
657
+ return int(self._network_timeout) if self._network_timeout is not None else None
658
+
659
+ @property
660
+ def socket_timeout(self) -> int | None:
661
+ return int(self._socket_timeout) if self._socket_timeout is not None else None
662
+
663
+ @property
664
+ def _backoff_generator(self) -> Iterator:
665
+ return self._backoff_policy()
666
+
667
+ @property
668
+ def client_session_keep_alive(self) -> bool | None:
669
+ return self._client_session_keep_alive
670
+
671
+ @client_session_keep_alive.setter
672
+ def client_session_keep_alive(self, value) -> None:
673
+ self._client_session_keep_alive = value
674
+
675
+ @property
676
+ def client_session_keep_alive_heartbeat_frequency(self) -> int | None:
677
+ return self._client_session_keep_alive_heartbeat_frequency
678
+
679
+ @client_session_keep_alive_heartbeat_frequency.setter
680
+ def client_session_keep_alive_heartbeat_frequency(self, value) -> None:
681
+ self._client_session_keep_alive_heartbeat_frequency = value
682
+ self._validate_client_session_keep_alive_heartbeat_frequency()
683
+
684
+ @property
685
+ def client_prefetch_threads(self) -> int:
686
+ return (
687
+ self._client_prefetch_threads
688
+ if self._client_prefetch_threads
689
+ else DEFAULT_CLIENT_PREFETCH_THREADS
690
+ )
691
+
692
+ @client_prefetch_threads.setter
693
+ def client_prefetch_threads(self, value) -> None:
694
+ self._client_prefetch_threads = value
695
+ self._validate_client_prefetch_threads()
696
+
697
+ @property
698
+ def client_fetch_threads(self) -> int | None:
699
+ return self._client_fetch_threads
700
+
701
+ @client_fetch_threads.setter
702
+ def client_fetch_threads(self, value: None | int) -> None:
703
+ if value is not None:
704
+ value = min(max(1, value), MAX_CLIENT_FETCH_THREADS)
705
+ self._client_fetch_threads = value
706
+
707
+ @property
708
+ def client_fetch_use_mp(self) -> bool:
709
+ return self._client_fetch_use_mp
710
+
711
+ @property
712
+ def rest(self) -> SnowflakeRestful | None:
713
+ return self._rest
714
+
715
+ @property
716
+ def application(self) -> str:
717
+ return self._application
718
+
719
+ @property
720
+ def errorhandler(self) -> Callable: # TODO: callable args
721
+ return self._errorhandler
722
+
723
+ @errorhandler.setter
724
+ # Note: Callable doesn't implement operator|
725
+ def errorhandler(self, value: Callable | None) -> None:
726
+ if value is None:
727
+ raise ProgrammingError("None errorhandler is specified")
728
+ self._errorhandler = value
729
+
730
+ @property
731
+ def converter_class(self) -> type[SnowflakeConverter]:
732
+ return self._converter_class
733
+
734
+ @property
735
+ def validate_default_parameters(self) -> bool:
736
+ return self._validate_default_parameters
737
+
738
+ @property
739
+ def is_pyformat(self) -> bool:
740
+ return self._paramstyle in ("pyformat", "format")
741
+
742
+ @property
743
+ def consent_cache_id_token(self):
744
+ return self._consent_cache_id_token
745
+
746
+ @property
747
+ def telemetry_enabled(self) -> bool:
748
+ return bool(
749
+ self._client_param_telemetry_enabled
750
+ and self._server_param_telemetry_enabled
751
+ )
752
+
753
+ @telemetry_enabled.setter
754
+ def telemetry_enabled(self, value) -> None:
755
+ self._client_param_telemetry_enabled = True if value else False
756
+ if (
757
+ self._client_param_telemetry_enabled
758
+ and not self._server_param_telemetry_enabled
759
+ ):
760
+ logger.info(
761
+ "Telemetry has been disabled by the session parameter CLIENT_TELEMETRY_ENABLED."
762
+ " Set session parameter CLIENT_TELEMETRY_ENABLED to true to enable telemetry."
763
+ )
764
+
765
+ @property
766
+ def service_name(self) -> str | None:
767
+ return self._service_name
768
+
769
+ @service_name.setter
770
+ def service_name(self, value) -> None:
771
+ self._service_name = value
772
+
773
+ @property
774
+ def log_max_query_length(self) -> int:
775
+ return self._log_max_query_length
776
+
777
+ @property
778
+ def disable_request_pooling(self) -> bool:
779
+ return self._disable_request_pooling
780
+
781
+ @disable_request_pooling.setter
782
+ def disable_request_pooling(self, value) -> None:
783
+ self._disable_request_pooling = True if value else False
784
+
785
+ @property
786
+ def use_openssl_only(self) -> bool:
787
+ # Deprecated, kept for backwards compatibility
788
+ return True
789
+
790
+ @property
791
+ def arrow_number_to_decimal(self):
792
+ return self._arrow_number_to_decimal
793
+
794
+ @property
795
+ def enable_stage_s3_privatelink_for_us_east_1(self) -> bool:
796
+ return self._enable_stage_s3_privatelink_for_us_east_1
797
+
798
+ @enable_stage_s3_privatelink_for_us_east_1.setter
799
+ def enable_stage_s3_privatelink_for_us_east_1(self, value) -> None:
800
+ self._enable_stage_s3_privatelink_for_us_east_1 = True if value else False
801
+
802
+ @property
803
+ def enable_connection_diag(self) -> bool:
804
+ return self._enable_connection_diag
805
+
806
+ @property
807
+ def connection_diag_log_path(self):
808
+ return self._connection_diag_log_path
809
+
810
+ @property
811
+ def connection_diag_whitelist_path(self):
812
+ """
813
+ Old version of ``connection_diag_allowlist_path``.
814
+ This used to be the original name, but snowflake backend
815
+ deprecated whitelist for allowlist. This name will be
816
+ deprecated in the future.
817
+ """
818
+ warnings.warn(
819
+ "connection_diag_whitelist_path has been deprecated, use connection_diag_allowlist_path instead",
820
+ DeprecationWarning,
821
+ stacklevel=2,
822
+ )
823
+ return self._connection_diag_whitelist_path
824
+
825
+ @property
826
+ def connection_diag_allowlist_path(self):
827
+ return self._connection_diag_allowlist_path
828
+
829
+ @arrow_number_to_decimal.setter
830
+ def arrow_number_to_decimal_setter(self, value: bool) -> None:
831
+ self._arrow_number_to_decimal = value
832
+
833
+ @property
834
+ def auth_class(self) -> AuthByPlugin | None:
835
+ return self._auth_class
836
+
837
+ @auth_class.setter
838
+ def auth_class(self, value: AuthByPlugin) -> None:
839
+ if isinstance(value, AuthByPlugin):
840
+ self._auth_class = value
841
+ else:
842
+ raise TypeError("auth_class must subclass AuthByPlugin")
843
+
844
+ @property
845
+ def is_query_context_cache_disabled(self) -> bool:
846
+ return self._disable_query_context_cache
847
+
848
+ @property
849
+ def iobound_tpe_limit(self) -> int | None:
850
+ return self._iobound_tpe_limit
851
+
852
+ @property
853
+ def unsafe_file_write(self) -> bool:
854
+ return self._unsafe_file_write
855
+
856
+ @unsafe_file_write.setter
857
+ def unsafe_file_write(self, value: bool) -> None:
858
+ self._unsafe_file_write = value
859
+
860
+ @property
861
+ def check_arrow_conversion_error_on_every_column(self) -> bool:
862
+ return self._check_arrow_conversion_error_on_every_column
863
+
864
+ @check_arrow_conversion_error_on_every_column.setter
865
+ def check_arrow_conversion_error_on_every_column(self, value: bool) -> bool:
866
+ self._check_arrow_conversion_error_on_every_column = value
867
+
868
+ def connect(self, **kwargs) -> None:
869
+ """Establishes connection to Snowflake."""
870
+ logger.debug("connect")
871
+ if len(kwargs) > 0:
872
+ self.__config(**kwargs)
873
+
874
+ if self.enable_connection_diag:
875
+ exceptions_dict = {}
876
+ connection_diag = ConnectionDiagnostic(
877
+ account=self.account,
878
+ host=self.host,
879
+ connection_diag_log_path=self.connection_diag_log_path,
880
+ connection_diag_allowlist_path=(
881
+ self.connection_diag_allowlist_path
882
+ if self.connection_diag_allowlist_path is not None
883
+ else self.connection_diag_whitelist_path
884
+ ),
885
+ proxy_host=self.proxy_host,
886
+ proxy_port=self.proxy_port,
887
+ proxy_user=self.proxy_user,
888
+ proxy_password=self.proxy_password,
889
+ )
890
+ try:
891
+ connection_diag.run_test()
892
+ self.__open_connection()
893
+ connection_diag.cursor = self.cursor()
894
+ except Exception:
895
+ exceptions_dict["connection_test"] = traceback.format_exc()
896
+ logger.warning(
897
+ f"""Exception during connection test:\n{exceptions_dict["connection_test"]} """
898
+ )
899
+ try:
900
+ connection_diag.run_post_test()
901
+ except Exception:
902
+ exceptions_dict["post_test"] = traceback.format_exc()
903
+ logger.warning(
904
+ f"""Exception during post connection test:\n{exceptions_dict["post_test"]} """
905
+ )
906
+ finally:
907
+ connection_diag.generate_report()
908
+ if exceptions_dict:
909
+ raise Exception(str(exceptions_dict))
910
+ else:
911
+ self.__open_connection()
912
+
913
+ def close(self, retry: bool = True) -> None:
914
+ """Closes the connection."""
915
+ # unregister to dereference connection object as it's already closed after the execution
916
+ atexit.unregister(self._close_at_exit)
917
+ try:
918
+ if not self.rest:
919
+ logger.debug("Rest object has been destroyed, cannot close session")
920
+ return
921
+
922
+ # will hang if the application doesn't close the connection and
923
+ # CLIENT_SESSION_KEEP_ALIVE is set, because the heartbeat runs on
924
+ # a separate thread.
925
+ self._cancel_heartbeat()
926
+
927
+ # close telemetry first, since it needs rest to send remaining data
928
+ logger.debug("closed")
929
+ self._telemetry.close(send_on_close=bool(retry and self.telemetry_enabled))
930
+ if (
931
+ self._all_async_queries_finished()
932
+ and not self._server_session_keep_alive
933
+ ):
934
+ logger.debug("No async queries seem to be running, deleting session")
935
+ self.rest.delete_session(retry=retry)
936
+ else:
937
+ logger.debug(
938
+ "There are {} async queries still running, not deleting session".format(
939
+ len(self._async_sfqids)
940
+ )
941
+ )
942
+ self.rest.close()
943
+ self._rest = None
944
+ if self.query_context_cache:
945
+ self.query_context_cache.clear_cache()
946
+ del self.messages[:]
947
+ logger.debug("Session is closed")
948
+ except Exception as e:
949
+ logger.debug(
950
+ "Exception encountered in closing connection. ignoring...: %s", e
951
+ )
952
+
953
+ def is_closed(self) -> bool:
954
+ """Checks whether the connection has been closed."""
955
+ return self.rest is None
956
+
957
+ def autocommit(self, mode) -> None:
958
+ """Sets autocommit mode to True, or False. Defaults to True."""
959
+ if not self.rest:
960
+ Error.errorhandler_wrapper(
961
+ self,
962
+ None,
963
+ DatabaseError,
964
+ {
965
+ "msg": "Connection is closed",
966
+ "errno": ER_CONNECTION_IS_CLOSED,
967
+ "sqlstate": SQLSTATE_CONNECTION_NOT_EXISTS,
968
+ },
969
+ )
970
+ if not isinstance(mode, bool):
971
+ Error.errorhandler_wrapper(
972
+ self,
973
+ None,
974
+ ProgrammingError,
975
+ {
976
+ "msg": f"Invalid parameter: {mode}",
977
+ "errno": ER_INVALID_VALUE,
978
+ },
979
+ )
980
+ try:
981
+ self.cursor().execute(f"ALTER SESSION SET autocommit={mode}")
982
+ except Error as e:
983
+ if e.sqlstate == SQLSTATE_FEATURE_NOT_SUPPORTED:
984
+ logger.debug(
985
+ "Autocommit feature is not enabled for this " "connection. Ignored"
986
+ )
987
+
988
+ def commit(self) -> None:
989
+ """Commits the current transaction."""
990
+ self.cursor().execute("COMMIT")
991
+
992
+ def rollback(self) -> None:
993
+ """Rolls back the current transaction."""
994
+ self.cursor().execute("ROLLBACK")
995
+
996
+ def cursor(
997
+ self, cursor_class: type[SnowflakeCursor] = SnowflakeCursor
998
+ ) -> SnowflakeCursor:
999
+ """Creates a cursor object. Each statement will be executed in a new cursor object."""
1000
+ logger.debug("cursor")
1001
+ if not self.rest:
1002
+ Error.errorhandler_wrapper(
1003
+ self,
1004
+ None,
1005
+ DatabaseError,
1006
+ {
1007
+ "msg": "Connection is closed",
1008
+ "errno": ER_CONNECTION_IS_CLOSED,
1009
+ "sqlstate": SQLSTATE_CONNECTION_NOT_EXISTS,
1010
+ },
1011
+ )
1012
+ return cursor_class(self)
1013
+
1014
+ def execute_string(
1015
+ self,
1016
+ sql_text: str,
1017
+ remove_comments: bool = False,
1018
+ return_cursors: bool = True,
1019
+ cursor_class: SnowflakeCursor = SnowflakeCursor,
1020
+ **kwargs,
1021
+ ) -> Iterable[SnowflakeCursor]:
1022
+ """Executes a SQL text including multiple statements. This is a non-standard convenience method."""
1023
+ stream = StringIO(sql_text)
1024
+ stream_generator = self.execute_stream(
1025
+ stream, remove_comments=remove_comments, cursor_class=cursor_class, **kwargs
1026
+ )
1027
+ ret = list(stream_generator)
1028
+ return ret if return_cursors else list()
1029
+
1030
+ def execute_stream(
1031
+ self,
1032
+ stream: StringIO,
1033
+ remove_comments: bool = False,
1034
+ cursor_class: SnowflakeCursor = SnowflakeCursor,
1035
+ **kwargs,
1036
+ ) -> Generator[SnowflakeCursor]:
1037
+ """Executes a stream of SQL statements. This is a non-standard convenient method."""
1038
+ split_statements_list = split_statements(
1039
+ stream, remove_comments=remove_comments
1040
+ )
1041
+ # Note: split_statements_list is a list of tuples of sql statements and whether they are put/get
1042
+ non_empty_statements = [e for e in split_statements_list if e[0]]
1043
+ for sql, is_put_or_get in non_empty_statements:
1044
+ cur = self.cursor(cursor_class=cursor_class)
1045
+ cur.execute(sql, _is_put_get=is_put_or_get, **kwargs)
1046
+ yield cur
1047
+
1048
+ def __set_error_attributes(self) -> None:
1049
+ for m in [
1050
+ method for method in dir(errors) if callable(getattr(errors, method))
1051
+ ]:
1052
+ # If name starts with _ then ignore that
1053
+ name = m if not m.startswith("_") else m[1:]
1054
+ setattr(self, name, getattr(errors, m))
1055
+
1056
+ @staticmethod
1057
+ def setup_ocsp_privatelink(app, hostname) -> None:
1058
+ hostname = hostname.lower()
1059
+ SnowflakeConnection.OCSP_ENV_LOCK.acquire()
1060
+ ocsp_cache_server = f"http://ocsp.{hostname}/ocsp_response_cache.json"
1061
+ os.environ["SF_OCSP_RESPONSE_CACHE_SERVER_URL"] = ocsp_cache_server
1062
+ logger.debug("OCSP Cache Server is updated: %s", ocsp_cache_server)
1063
+ SnowflakeConnection.OCSP_ENV_LOCK.release()
1064
+
1065
+ def __open_connection(self):
1066
+ """Opens a new network connection."""
1067
+ self.converter = self._converter_class(
1068
+ use_numpy=self._numpy, support_negative_year=self._support_negative_year
1069
+ )
1070
+
1071
+ proxy.set_proxies(
1072
+ self.proxy_host, self.proxy_port, self.proxy_user, self.proxy_password
1073
+ )
1074
+
1075
+ self._rest = SnowflakeRestful(
1076
+ host=self.host,
1077
+ port=self.port,
1078
+ protocol=self._protocol,
1079
+ inject_client_pause=self._inject_client_pause,
1080
+ connection=self,
1081
+ )
1082
+ logger.debug("REST API object was created: %s:%s", self.host, self.port)
1083
+
1084
+ if "SF_OCSP_RESPONSE_CACHE_SERVER_URL" in os.environ:
1085
+ logger.debug(
1086
+ "Custom OCSP Cache Server URL found in environment - %s",
1087
+ os.environ["SF_OCSP_RESPONSE_CACHE_SERVER_URL"],
1088
+ )
1089
+
1090
+ if ".privatelink.snowflakecomputing." in self.host.lower():
1091
+ SnowflakeConnection.setup_ocsp_privatelink(self.application, self.host)
1092
+ else:
1093
+ if "SF_OCSP_RESPONSE_CACHE_SERVER_URL" in os.environ:
1094
+ del os.environ["SF_OCSP_RESPONSE_CACHE_SERVER_URL"]
1095
+
1096
+ if self._session_parameters is None:
1097
+ self._session_parameters = {}
1098
+ if self._autocommit is not None:
1099
+ self._session_parameters[PARAMETER_AUTOCOMMIT] = self._autocommit
1100
+
1101
+ if self._timezone is not None:
1102
+ self._session_parameters[PARAMETER_TIMEZONE] = self._timezone
1103
+
1104
+ if self._validate_default_parameters:
1105
+ # Snowflake will validate the requested database, schema, and warehouse
1106
+ self._session_parameters[PARAMETER_CLIENT_VALIDATE_DEFAULT_PARAMETERS] = (
1107
+ True
1108
+ )
1109
+
1110
+ if self.client_session_keep_alive is not None:
1111
+ self._session_parameters[PARAMETER_CLIENT_SESSION_KEEP_ALIVE] = (
1112
+ self._client_session_keep_alive
1113
+ )
1114
+
1115
+ if self.client_session_keep_alive_heartbeat_frequency is not None:
1116
+ self._session_parameters[
1117
+ PARAMETER_CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY
1118
+ ] = self._validate_client_session_keep_alive_heartbeat_frequency()
1119
+
1120
+ if self.client_prefetch_threads:
1121
+ self._session_parameters[PARAMETER_CLIENT_PREFETCH_THREADS] = (
1122
+ self._validate_client_prefetch_threads()
1123
+ )
1124
+
1125
+ # Setup authenticator
1126
+ auth = Auth(self.rest)
1127
+
1128
+ if self._session_token and self._master_token:
1129
+ auth._rest.update_tokens(
1130
+ self._session_token,
1131
+ self._master_token,
1132
+ self._master_validity_in_seconds,
1133
+ )
1134
+ heartbeat_ret = auth._rest._heartbeat()
1135
+ logger.debug(heartbeat_ret)
1136
+ if not heartbeat_ret or not heartbeat_ret.get("success"):
1137
+ Error.errorhandler_wrapper(
1138
+ self,
1139
+ None,
1140
+ ProgrammingError,
1141
+ {
1142
+ "msg": "Session and master tokens invalid",
1143
+ "errno": ER_INVALID_VALUE,
1144
+ },
1145
+ )
1146
+ else:
1147
+ logger.debug("Session and master token validation successful.")
1148
+
1149
+ else:
1150
+ if self.auth_class is not None:
1151
+ if type(
1152
+ self.auth_class
1153
+ ) not in FIRST_PARTY_AUTHENTICATORS and not issubclass(
1154
+ type(self.auth_class), AuthByKeyPair
1155
+ ):
1156
+ raise TypeError("auth_class must be a child class of AuthByKeyPair")
1157
+ # TODO: add telemetry for custom auth
1158
+ self.auth_class = self.auth_class
1159
+ elif self._authenticator == DEFAULT_AUTHENTICATOR:
1160
+ self.auth_class = AuthByDefault(
1161
+ password=self._password,
1162
+ timeout=self.login_timeout,
1163
+ backoff_generator=self._backoff_generator,
1164
+ )
1165
+ elif self._authenticator == EXTERNAL_BROWSER_AUTHENTICATOR:
1166
+ self._session_parameters[
1167
+ PARAMETER_CLIENT_STORE_TEMPORARY_CREDENTIAL
1168
+ ] = (self._client_store_temporary_credential if IS_LINUX else True)
1169
+ auth.read_temporary_credentials(
1170
+ self.host,
1171
+ self.user,
1172
+ self._session_parameters,
1173
+ )
1174
+ # Depending on whether self._rest.id_token is available we do different
1175
+ # auth_instance
1176
+ if self._rest.id_token is None:
1177
+ self.auth_class = AuthByWebBrowser(
1178
+ application=self.application,
1179
+ protocol=self._protocol,
1180
+ host=self.host, # TODO: delete this?
1181
+ port=self.port,
1182
+ timeout=self.login_timeout,
1183
+ backoff_generator=self._backoff_generator,
1184
+ )
1185
+ else:
1186
+ self.auth_class = AuthByIdToken(
1187
+ id_token=self._rest.id_token,
1188
+ application=self.application,
1189
+ protocol=self._protocol,
1190
+ host=self.host,
1191
+ port=self.port,
1192
+ timeout=self.login_timeout,
1193
+ backoff_generator=self._backoff_generator,
1194
+ )
1195
+
1196
+ elif self._authenticator == KEY_PAIR_AUTHENTICATOR:
1197
+ private_key = self._private_key
1198
+
1199
+ if self._private_key_file:
1200
+ private_key = _get_private_bytes_from_file(
1201
+ self._private_key_file,
1202
+ self._private_key_file_pwd,
1203
+ )
1204
+
1205
+ self.auth_class = AuthByKeyPair(
1206
+ private_key=private_key,
1207
+ timeout=self.login_timeout,
1208
+ backoff_generator=self._backoff_generator,
1209
+ )
1210
+ elif self._authenticator == OAUTH_AUTHENTICATOR:
1211
+ self.auth_class = AuthByOAuth(
1212
+ oauth_token=self._token,
1213
+ timeout=self.login_timeout,
1214
+ backoff_generator=self._backoff_generator,
1215
+ )
1216
+ elif self._authenticator == OAUTH_AUTHORIZATION_CODE:
1217
+ if self._role and (self._oauth_scope == ""):
1218
+ # if role is known then let's inject it into scope
1219
+ self._oauth_scope = _OAUTH_DEFAULT_SCOPE.format(role=self._role)
1220
+ self.auth_class = AuthByOauthCode(
1221
+ application=self.application,
1222
+ client_id=self._oauth_client_id,
1223
+ client_secret=self._oauth_client_secret,
1224
+ host=self.host,
1225
+ authentication_url=self._oauth_authorization_url.format(
1226
+ host=self.host, port=self.port
1227
+ ),
1228
+ token_request_url=self._oauth_token_request_url.format(
1229
+ host=self.host, port=self.port
1230
+ ),
1231
+ redirect_uri=self._oauth_redirect_uri,
1232
+ scope=self._oauth_scope,
1233
+ pkce_enabled=not self._oauth_disable_pkce,
1234
+ token_cache=(
1235
+ auth.get_token_cache()
1236
+ if self._client_store_temporary_credential
1237
+ else None
1238
+ ),
1239
+ refresh_token_enabled=self._oauth_enable_refresh_tokens,
1240
+ external_browser_timeout=self._external_browser_timeout,
1241
+ enable_single_use_refresh_tokens=self._oauth_enable_single_use_refresh_tokens,
1242
+ )
1243
+ elif self._authenticator == OAUTH_CLIENT_CREDENTIALS:
1244
+ if self._role and (self._oauth_scope == ""):
1245
+ # if role is known then let's inject it into scope
1246
+ self._oauth_scope = _OAUTH_DEFAULT_SCOPE.format(role=self._role)
1247
+ self.auth_class = AuthByOauthCredentials(
1248
+ application=self.application,
1249
+ client_id=self._oauth_client_id,
1250
+ client_secret=self._oauth_client_secret,
1251
+ token_request_url=self._oauth_token_request_url.format(
1252
+ host=self.host, port=self.port
1253
+ ),
1254
+ scope=self._oauth_scope,
1255
+ token_cache=(
1256
+ auth.get_token_cache()
1257
+ if self._client_store_temporary_credential
1258
+ else None
1259
+ ),
1260
+ refresh_token_enabled=self._oauth_enable_refresh_tokens,
1261
+ connection=self,
1262
+ )
1263
+ elif self._authenticator == USR_PWD_MFA_AUTHENTICATOR:
1264
+ self._session_parameters[PARAMETER_CLIENT_REQUEST_MFA_TOKEN] = (
1265
+ self._client_request_mfa_token if IS_LINUX else True
1266
+ )
1267
+ if self._session_parameters[PARAMETER_CLIENT_REQUEST_MFA_TOKEN]:
1268
+ auth.read_temporary_credentials(
1269
+ self.host,
1270
+ self.user,
1271
+ self._session_parameters,
1272
+ )
1273
+ self.auth_class = AuthByUsrPwdMfa(
1274
+ password=self._password,
1275
+ mfa_token=self.rest.mfa_token,
1276
+ timeout=self.login_timeout,
1277
+ backoff_generator=self._backoff_generator,
1278
+ )
1279
+ elif self._authenticator == PROGRAMMATIC_ACCESS_TOKEN:
1280
+ self.auth_class = AuthByPAT(self._token)
1281
+ elif self._authenticator == PAT_WITH_EXTERNAL_SESSION:
1282
+ # We don't need to do a POST to /v1/login-request to get session and master tokens at the startup
1283
+ # time. PAT with external (Spark) session ID creates a new session when it encounters the unique
1284
+ # (PAT, external session ID) combination for the first time and then onwards use the (PAT, external
1285
+ # session id) as a key to identify and authenticate the session. So we bypass actual AuthN here.
1286
+ self.auth_class = AuthNoAuth()
1287
+ self._rest.set_pat_and_external_session(
1288
+ self._token, self._external_session_id
1289
+ )
1290
+ elif self._authenticator == WORKLOAD_IDENTITY_AUTHENTICATOR:
1291
+ self._check_experimental_authentication_flag()
1292
+ # Standardize the provider enum.
1293
+ if self._workload_identity_provider and isinstance(
1294
+ self._workload_identity_provider, str
1295
+ ):
1296
+ self._workload_identity_provider = AttestationProvider.from_string(
1297
+ self._workload_identity_provider
1298
+ )
1299
+ self.auth_class = AuthByWorkloadIdentity(
1300
+ provider=self._workload_identity_provider,
1301
+ token=self._token,
1302
+ entra_resource=self._workload_identity_entra_resource,
1303
+ )
1304
+ else:
1305
+ # okta URL, e.g., https://<account>.okta.com/
1306
+ self.auth_class = AuthByOkta(
1307
+ application=self.application,
1308
+ timeout=self.login_timeout,
1309
+ backoff_generator=self._backoff_generator,
1310
+ )
1311
+
1312
+ self.authenticate_with_retry(self.auth_class)
1313
+
1314
+ self._password = None # ensure password won't persist
1315
+ self.auth_class.reset_secrets()
1316
+
1317
+ self.initialize_query_context_cache()
1318
+
1319
+ if self.client_session_keep_alive:
1320
+ # This will be called after the heartbeat frequency has actually been set.
1321
+ # By this point it should have been decided if the heartbeat has to be enabled
1322
+ # and what would the heartbeat frequency be
1323
+ self._add_heartbeat()
1324
+
1325
+ def __config(self, **kwargs):
1326
+ """Sets up parameters in the connection object."""
1327
+ logger.debug("__config")
1328
+ # Handle special cases first
1329
+ if "sequence_counter" in kwargs:
1330
+ self.sequence_counter = kwargs["sequence_counter"]
1331
+ if "application" in kwargs:
1332
+ value = kwargs["application"]
1333
+ if not APPLICATION_RE.match(value):
1334
+ msg = f"Invalid application name: {value}"
1335
+ raise ProgrammingError(msg=msg, errno=0)
1336
+ else:
1337
+ self._application = value
1338
+ if "validate_default_parameters" in kwargs:
1339
+ self._validate_default_parameters = kwargs["validate_default_parameters"]
1340
+ # Handle rest of arguments
1341
+ skip_list = ["validate_default_parameters", "sequence_counter", "application"]
1342
+ for name, value in filter(lambda e: e[0] not in skip_list, kwargs.items()):
1343
+ if self.validate_default_parameters:
1344
+ if name not in DEFAULT_CONFIGURATION.keys():
1345
+ close_matches = get_close_matches(
1346
+ name, DEFAULT_CONFIGURATION.keys(), n=1, cutoff=0.8
1347
+ )
1348
+ guess = close_matches[0] if len(close_matches) > 0 else None
1349
+ warnings.warn(
1350
+ "'{}' is an unknown connection parameter{}".format(
1351
+ name, f", did you mean '{guess}'?" if guess else ""
1352
+ ),
1353
+ # Raise warning from where class was initiated
1354
+ stacklevel=4,
1355
+ )
1356
+ elif not isinstance(value, DEFAULT_CONFIGURATION[name][1]):
1357
+ accepted_types = DEFAULT_CONFIGURATION[name][1]
1358
+ warnings.warn(
1359
+ "'{}' connection parameter should be of type '{}', but is a '{}'".format(
1360
+ name,
1361
+ (
1362
+ str(tuple(e.__name__ for e in accepted_types)).replace(
1363
+ "'", ""
1364
+ )
1365
+ if isinstance(accepted_types, tuple)
1366
+ else accepted_types.__name__
1367
+ ),
1368
+ type(value).__name__,
1369
+ ),
1370
+ # Raise warning from where class was initiated
1371
+ stacklevel=4,
1372
+ )
1373
+ setattr(self, "_" + name, value)
1374
+
1375
+ if self._numpy:
1376
+ try:
1377
+ import numpy # noqa: F401
1378
+ except ModuleNotFoundError: # pragma: no cover
1379
+ Error.errorhandler_wrapper(
1380
+ self,
1381
+ None,
1382
+ ProgrammingError,
1383
+ {
1384
+ "msg": "Numpy module is not installed. Cannot fetch data as numpy",
1385
+ "errno": ER_NO_NUMPY,
1386
+ },
1387
+ )
1388
+
1389
+ if self._paramstyle is None:
1390
+ import snowflake.connector
1391
+
1392
+ self._paramstyle = snowflake.connector.paramstyle
1393
+ elif self._paramstyle not in SUPPORTED_PARAMSTYLES:
1394
+ raise ProgrammingError(
1395
+ msg="Invalid paramstyle is specified", errno=ER_INVALID_VALUE
1396
+ )
1397
+
1398
+ if self._auth_class and not isinstance(self._auth_class, AuthByPlugin):
1399
+ raise TypeError("auth_class must subclass AuthByPlugin")
1400
+
1401
+ if "account" in kwargs:
1402
+ if "host" not in kwargs:
1403
+ self._host = construct_hostname(kwargs.get("region"), self._account)
1404
+
1405
+ if "unsafe_file_write" in kwargs:
1406
+ self._unsafe_file_write = kwargs["unsafe_file_write"]
1407
+ else:
1408
+ self._unsafe_file_write = False
1409
+
1410
+ logger.info(
1411
+ f"Connecting to {_DOMAIN_NAME_MAP.get(extract_top_level_domain_from_hostname(self._host), 'GLOBAL')} Snowflake domain"
1412
+ )
1413
+
1414
+ # If using a custom auth class, we should set the authenticator
1415
+ # type to be the same as the custom auth class
1416
+ if self._auth_class:
1417
+ self._authenticator = self._auth_class.type_.value
1418
+
1419
+ if self._authenticator:
1420
+ # Only upper self._authenticator if it is a non-okta link
1421
+ auth_tmp = self._authenticator.upper()
1422
+ if auth_tmp in [ # Non-okta authenticators
1423
+ DEFAULT_AUTHENTICATOR,
1424
+ EXTERNAL_BROWSER_AUTHENTICATOR,
1425
+ KEY_PAIR_AUTHENTICATOR,
1426
+ OAUTH_AUTHENTICATOR,
1427
+ USR_PWD_MFA_AUTHENTICATOR,
1428
+ WORKLOAD_IDENTITY_AUTHENTICATOR,
1429
+ PAT_WITH_EXTERNAL_SESSION,
1430
+ ]:
1431
+ self._authenticator = auth_tmp
1432
+
1433
+ # read OAuth token from
1434
+ token_file_path = kwargs.get("token_file_path")
1435
+ if token_file_path:
1436
+ with open(token_file_path) as f:
1437
+ self._token = f.read()
1438
+
1439
+ # Set of authenticators allowing empty user.
1440
+ empty_user_allowed_authenticators = {
1441
+ OAUTH_AUTHENTICATOR,
1442
+ NO_AUTH_AUTHENTICATOR,
1443
+ WORKLOAD_IDENTITY_AUTHENTICATOR,
1444
+ PROGRAMMATIC_ACCESS_TOKEN,
1445
+ PAT_WITH_EXTERNAL_SESSION,
1446
+ }
1447
+
1448
+ if not (self._master_token and self._session_token):
1449
+ if (
1450
+ not self.user
1451
+ and self._authenticator not in empty_user_allowed_authenticators
1452
+ ):
1453
+ # Some authenticators do not require a username
1454
+ Error.errorhandler_wrapper(
1455
+ self,
1456
+ None,
1457
+ ProgrammingError,
1458
+ {"msg": "User is empty", "errno": ER_NO_USER},
1459
+ )
1460
+
1461
+ if self._private_key or self._private_key_file:
1462
+ self._authenticator = KEY_PAIR_AUTHENTICATOR
1463
+
1464
+ workload_identity_dependent_options = [
1465
+ "workload_identity_provider",
1466
+ "workload_identity_entra_resource",
1467
+ ]
1468
+ for dependent_option in workload_identity_dependent_options:
1469
+ if (
1470
+ self.__getattribute__(f"_{dependent_option}") is not None
1471
+ and self._authenticator != WORKLOAD_IDENTITY_AUTHENTICATOR
1472
+ ):
1473
+ Error.errorhandler_wrapper(
1474
+ self,
1475
+ None,
1476
+ ProgrammingError,
1477
+ {
1478
+ "msg": f"{dependent_option} was set but authenticator was not set to {WORKLOAD_IDENTITY_AUTHENTICATOR}",
1479
+ "errno": ER_INVALID_WIF_SETTINGS,
1480
+ },
1481
+ )
1482
+
1483
+ if (
1484
+ self.auth_class is None
1485
+ and self._authenticator
1486
+ not in (
1487
+ EXTERNAL_BROWSER_AUTHENTICATOR,
1488
+ OAUTH_AUTHENTICATOR,
1489
+ OAUTH_AUTHORIZATION_CODE,
1490
+ OAUTH_CLIENT_CREDENTIALS,
1491
+ KEY_PAIR_AUTHENTICATOR,
1492
+ PROGRAMMATIC_ACCESS_TOKEN,
1493
+ WORKLOAD_IDENTITY_AUTHENTICATOR,
1494
+ PAT_WITH_EXTERNAL_SESSION,
1495
+ )
1496
+ and not self._password
1497
+ ):
1498
+ Error.errorhandler_wrapper(
1499
+ self,
1500
+ None,
1501
+ ProgrammingError,
1502
+ {"msg": "Password is empty", "errno": ER_NO_PASSWORD},
1503
+ )
1504
+
1505
+ # Only AuthNoAuth allows account to be omitted.
1506
+ if not self._account and not isinstance(self.auth_class, AuthNoAuth):
1507
+ Error.errorhandler_wrapper(
1508
+ self,
1509
+ None,
1510
+ ProgrammingError,
1511
+ {"msg": "Account must be specified", "errno": ER_NO_ACCOUNT_NAME},
1512
+ )
1513
+ if self._account and "." in self._account:
1514
+ self._account = parse_account(self._account)
1515
+
1516
+ if not isinstance(self._backoff_policy, Callable) or not isinstance(
1517
+ self._backoff_policy(), Iterator
1518
+ ):
1519
+ Error.errorhandler_wrapper(
1520
+ self,
1521
+ None,
1522
+ ProgrammingError,
1523
+ {
1524
+ "msg": "Backoff policy must be a generator function",
1525
+ "errno": ER_INVALID_BACKOFF_POLICY,
1526
+ },
1527
+ )
1528
+
1529
+ if self.ocsp_fail_open:
1530
+ logger.debug(
1531
+ "This connection is in OCSP Fail Open Mode. "
1532
+ "TLS Certificates would be checked for validity "
1533
+ "and revocation status. Any other Certificate "
1534
+ "Revocation related exceptions or OCSP Responder "
1535
+ "failures would be disregarded in favor of "
1536
+ "connectivity."
1537
+ )
1538
+
1539
+ if self.disable_ocsp_checks:
1540
+ logger.debug(
1541
+ "This connection runs with disabled OCSP checks. "
1542
+ "Revocation status of the certificate will not be checked against OCSP Responder."
1543
+ )
1544
+
1545
+ def cmd_query(
1546
+ self,
1547
+ sql: str,
1548
+ sequence_counter: int,
1549
+ request_id: uuid.UUID,
1550
+ binding_params: None | tuple | dict[str, dict[str, str]] = None,
1551
+ binding_stage: str | None = None,
1552
+ is_file_transfer: bool = False,
1553
+ statement_params: dict[str, str] | None = None,
1554
+ is_internal: bool = False,
1555
+ describe_only: bool = False,
1556
+ _no_results: bool = False,
1557
+ _update_current_object: bool = True,
1558
+ _no_retry: bool = False,
1559
+ timeout: int | None = None,
1560
+ dataframe_ast: str | None = None,
1561
+ ) -> dict[str, Any]:
1562
+ """Executes a query with a sequence counter."""
1563
+ logger.debug("_cmd_query")
1564
+ data = {
1565
+ "sqlText": sql,
1566
+ "asyncExec": _no_results,
1567
+ "sequenceId": sequence_counter,
1568
+ "querySubmissionTime": get_time_millis(),
1569
+ }
1570
+ if dataframe_ast is not None:
1571
+ data["dataframeAst"] = dataframe_ast
1572
+ if statement_params is not None:
1573
+ data["parameters"] = statement_params
1574
+ if is_internal:
1575
+ data["isInternal"] = is_internal
1576
+ if describe_only:
1577
+ data["describeOnly"] = describe_only
1578
+ if binding_stage is not None:
1579
+ # binding stage for bulk array binding
1580
+ data["bindStage"] = binding_stage
1581
+ if binding_params is not None:
1582
+ # binding parameters. This is for qmarks paramstyle.
1583
+ data["bindings"] = binding_params
1584
+ if not _no_results:
1585
+ # not an async query.
1586
+ queryContext = self.get_query_context()
1587
+ # Here queryContextDTO should be a dict object field, same with `parameters` field
1588
+ data["queryContextDTO"] = queryContext
1589
+ client = "sfsql_file_transfer" if is_file_transfer else "sfsql"
1590
+
1591
+ if logger.getEffectiveLevel() <= logging.DEBUG:
1592
+ logger.debug(
1593
+ "sql=[%s], sequence_id=[%s], is_file_transfer=[%s]",
1594
+ self._format_query_for_log(data["sqlText"]),
1595
+ data["sequenceId"],
1596
+ is_file_transfer,
1597
+ )
1598
+
1599
+ url_parameters = {REQUEST_ID: request_id}
1600
+
1601
+ ret = self.rest.request(
1602
+ "/queries/v1/query-request?" + urlencode(url_parameters),
1603
+ data,
1604
+ client=client,
1605
+ _no_results=_no_results,
1606
+ _include_retry_params=True,
1607
+ _no_retry=_no_retry,
1608
+ timeout=timeout,
1609
+ )
1610
+
1611
+ if ret is None:
1612
+ ret = {"data": {}}
1613
+ if ret.get("data") is None:
1614
+ ret["data"] = {}
1615
+ if _update_current_object:
1616
+ data = ret["data"]
1617
+ if "finalDatabaseName" in data and data["finalDatabaseName"] is not None:
1618
+ self._database = data["finalDatabaseName"]
1619
+ if "finalSchemaName" in data and data["finalSchemaName"] is not None:
1620
+ self._schema = data["finalSchemaName"]
1621
+ if "finalWarehouseName" in data and data["finalWarehouseName"] is not None:
1622
+ self._warehouse = data["finalWarehouseName"]
1623
+ if "finalRoleName" in data:
1624
+ self._role = data["finalRoleName"]
1625
+ if "queryContext" in data and not _no_results:
1626
+ # here the data["queryContext"] field has been automatically converted from JSON into a dict type
1627
+ self.set_query_context(data["queryContext"])
1628
+
1629
+ return ret
1630
+
1631
+ def _reauthenticate(self):
1632
+ return self._auth_class.reauthenticate(conn=self)
1633
+
1634
+ def authenticate_with_retry(self, auth_instance) -> None:
1635
+ # make some changes if needed before real __authenticate
1636
+ try:
1637
+ self._authenticate(auth_instance)
1638
+ except ReauthenticationRequest as ex:
1639
+ # cached id_token expiration error, we have cleaned id_token and try to authenticate again
1640
+ logger.debug("ID token expired. Reauthenticating...: %s", ex)
1641
+ if type(auth_instance) in (
1642
+ AuthByIdToken,
1643
+ AuthByOauthCode,
1644
+ AuthByOauthCredentials,
1645
+ ):
1646
+ # IDToken and OAuth auth need to authenticate through
1647
+ # SSO if its credential has expired
1648
+ self._reauthenticate()
1649
+ else:
1650
+ self._authenticate(auth_instance)
1651
+
1652
+ def _authenticate(self, auth_instance: AuthByPlugin):
1653
+ auth_instance.prepare(
1654
+ conn=self,
1655
+ authenticator=self._authenticator,
1656
+ service_name=self.service_name,
1657
+ account=self.account,
1658
+ user=self.user,
1659
+ password=self._password,
1660
+ )
1661
+ self._consent_cache_id_token = getattr(
1662
+ auth_instance, "consent_cache_id_token", True
1663
+ )
1664
+
1665
+ auth = Auth(self.rest)
1666
+ # record start time for computing timeout
1667
+ auth_instance._retry_ctx.set_start_time()
1668
+ try:
1669
+ auth.authenticate(
1670
+ auth_instance=auth_instance,
1671
+ account=self.account,
1672
+ user=self.user,
1673
+ database=self.database,
1674
+ schema=self.schema,
1675
+ warehouse=self.warehouse,
1676
+ role=self.role,
1677
+ passcode=self._passcode,
1678
+ passcode_in_password=self._passcode_in_password,
1679
+ mfa_callback=self._mfa_callback,
1680
+ password_callback=self._password_callback,
1681
+ session_parameters=self._session_parameters,
1682
+ )
1683
+ except OperationalError as e:
1684
+ logger.debug(
1685
+ "Operational Error raised at authentication"
1686
+ f"for authenticator: {type(auth_instance).__name__}"
1687
+ )
1688
+ while True:
1689
+ try:
1690
+ auth_instance.handle_timeout(
1691
+ authenticator=self._authenticator,
1692
+ service_name=self.service_name,
1693
+ account=self.account,
1694
+ user=self.user,
1695
+ password=self._password,
1696
+ )
1697
+ auth.authenticate(
1698
+ auth_instance=auth_instance,
1699
+ account=self.account,
1700
+ user=self.user,
1701
+ database=self.database,
1702
+ schema=self.schema,
1703
+ warehouse=self.warehouse,
1704
+ role=self.role,
1705
+ passcode=self._passcode,
1706
+ passcode_in_password=self._passcode_in_password,
1707
+ mfa_callback=self._mfa_callback,
1708
+ password_callback=self._password_callback,
1709
+ session_parameters=self._session_parameters,
1710
+ )
1711
+ except OperationalError as auth_op:
1712
+ if auth_op.errno == ER_FAILED_TO_CONNECT_TO_DB:
1713
+ if _CONNECTIVITY_ERR_MSG in e.msg:
1714
+ auth_op.msg += f"\n{_CONNECTIVITY_ERR_MSG}"
1715
+ raise auth_op from e
1716
+ logger.debug("Continuing authenticator specific timeout handling")
1717
+ continue
1718
+ break
1719
+
1720
+ def _write_params_to_byte_rows(
1721
+ self, params: list[tuple[Any | tuple]]
1722
+ ) -> list[bytes]:
1723
+ """Write csv-format rows of binding values as list of bytes string.
1724
+
1725
+ Args:
1726
+ params: Binding parameters to bulk array insertion query with qmark/numeric format.
1727
+ cursor: SnowflakeCursor.
1728
+
1729
+ Returns:
1730
+ List of bytes string corresponding to rows
1731
+
1732
+ """
1733
+ res = []
1734
+ try:
1735
+ for row in params:
1736
+ temp = map(self.converter.to_csv_bindings, row)
1737
+ res.append((",".join(temp) + "\n").encode("utf-8"))
1738
+ except (ProgrammingError, AttributeError) as exc:
1739
+ raise BindUploadError from exc
1740
+ return res
1741
+
1742
+ def _get_snowflake_type_and_binding(
1743
+ self,
1744
+ cursor: SnowflakeCursor | None,
1745
+ v: tuple[str, Any] | Any,
1746
+ ) -> TypeAndBinding:
1747
+ if isinstance(v, tuple):
1748
+ if len(v) != 2:
1749
+ Error.errorhandler_wrapper(
1750
+ self,
1751
+ cursor,
1752
+ ProgrammingError,
1753
+ {
1754
+ "msg": "Binding parameters must be a list "
1755
+ "where one element is a single value or "
1756
+ "a pair of Snowflake datatype and a value",
1757
+ "errno": ER_FAILED_PROCESSING_QMARK,
1758
+ },
1759
+ )
1760
+ snowflake_type, v = v
1761
+ else:
1762
+ snowflake_type = self.converter.snowflake_type(v)
1763
+ if snowflake_type is None:
1764
+ Error.errorhandler_wrapper(
1765
+ self,
1766
+ cursor,
1767
+ ProgrammingError,
1768
+ {
1769
+ "msg": "Python data type [{}] cannot be "
1770
+ "automatically mapped to Snowflake data "
1771
+ "type. Specify the snowflake data type "
1772
+ "explicitly.".format(v.__class__.__name__.lower()),
1773
+ "errno": ER_NOT_IMPLICITY_SNOWFLAKE_DATATYPE,
1774
+ },
1775
+ )
1776
+ return TypeAndBinding(
1777
+ snowflake_type,
1778
+ self.converter.to_snowflake_bindings(snowflake_type, v),
1779
+ )
1780
+
1781
+ # TODO we could probably rework this to not make dicts like this: {'1': 'value', '2': '13'}
1782
+ def _process_params_qmarks(
1783
+ self,
1784
+ params: Sequence | None,
1785
+ cursor: SnowflakeCursor | None = None,
1786
+ ) -> dict[str, dict[str, str]] | None:
1787
+ if not params:
1788
+ return None
1789
+ processed_params = {}
1790
+
1791
+ get_type_and_binding = partial(self._get_snowflake_type_and_binding, cursor)
1792
+
1793
+ for idx, v in enumerate(params):
1794
+ if isinstance(v, list):
1795
+ snowflake_type = self.converter.snowflake_type(v)
1796
+ all_param_data = list(map(get_type_and_binding, v))
1797
+ first_type = all_param_data[0].type
1798
+ # if all elements have the same snowflake type, update snowflake_type
1799
+ if all(param_data.type == first_type for param_data in all_param_data):
1800
+ snowflake_type = first_type
1801
+ processed_params[str(idx + 1)] = {
1802
+ "type": snowflake_type,
1803
+ "value": [param_data.binding for param_data in all_param_data],
1804
+ }
1805
+ else:
1806
+ snowflake_type, snowflake_binding = get_type_and_binding(v)
1807
+ processed_params[str(idx + 1)] = {
1808
+ "type": snowflake_type,
1809
+ "value": snowflake_binding,
1810
+ }
1811
+ if logger.getEffectiveLevel() <= logging.DEBUG:
1812
+ for k, v in processed_params.items():
1813
+ logger.debug("idx: %s, type: %s", k, v.get("type"))
1814
+ return processed_params
1815
+
1816
+ def _process_params_pyformat(
1817
+ self,
1818
+ params: Any | Sequence[Any] | dict[Any, Any] | None,
1819
+ cursor: SnowflakeCursor | None = None,
1820
+ ) -> tuple[Any] | dict[str, Any] | None:
1821
+ """Process parameters for client-side parameter binding.
1822
+
1823
+ Args:
1824
+ params: Either a sequence, or a dictionary of parameters, if anything else
1825
+ is given then it will be put into a list and processed that way.
1826
+ cursor: The SnowflakeCursor used to report errors if necessary.
1827
+ """
1828
+ if params is None:
1829
+ if self._interpolate_empty_sequences:
1830
+ return None
1831
+ return {}
1832
+ if isinstance(params, dict):
1833
+ return self._process_params_dict(params)
1834
+
1835
+ # TODO: remove this, callers should send in what's in the signature
1836
+ if not isinstance(params, (tuple, list)):
1837
+ params = [
1838
+ params,
1839
+ ]
1840
+
1841
+ try:
1842
+ res = map(self._process_single_param, params)
1843
+ ret = tuple(res)
1844
+ logger.debug(f"parameters: {ret}")
1845
+ return ret
1846
+ except Exception as e:
1847
+ Error.errorhandler_wrapper(
1848
+ self,
1849
+ cursor,
1850
+ ProgrammingError,
1851
+ {
1852
+ "msg": f"Failed processing pyformat-parameters; {e}",
1853
+ "errno": ER_FAILED_PROCESSING_PYFORMAT,
1854
+ },
1855
+ )
1856
+
1857
+ def _process_params_dict(
1858
+ self, params: dict[Any, Any], cursor: SnowflakeCursor | None = None
1859
+ ) -> dict:
1860
+ try:
1861
+ res = {k: self._process_single_param(v) for k, v in params.items()}
1862
+ logger.debug(f"parameters: {res}")
1863
+ return res
1864
+ except Exception as e:
1865
+ Error.errorhandler_wrapper(
1866
+ self,
1867
+ cursor,
1868
+ ProgrammingError,
1869
+ {
1870
+ "msg": f"Failed processing pyformat-parameters: {e}",
1871
+ "errno": ER_FAILED_PROCESSING_PYFORMAT,
1872
+ },
1873
+ )
1874
+
1875
+ def _process_single_param(self, param: Any) -> Any:
1876
+ """Process a single parameter to Snowflake understandable form.
1877
+
1878
+ This is a convenience function to replace repeated multiple calls with a single
1879
+ function call.
1880
+
1881
+ It calls the following underlying functions in this order:
1882
+ 1. self.converter.to_snowflake
1883
+ 2. self.converter.escape
1884
+ 3. self.converter.quote
1885
+ """
1886
+ to_snowflake = self.converter.to_snowflake
1887
+ escape = self.converter.escape
1888
+ _quote = self.converter.quote
1889
+ return _quote(escape(to_snowflake(param)))
1890
+
1891
+ def _cancel_query(self, sql: str, request_id: UUID) -> dict[str, bool | None]:
1892
+ """Cancels the query with the exact SQL query and requestId."""
1893
+ logger.debug("_cancel_query sql=[%s], request_id=[%s]", sql, request_id)
1894
+ url_parameters = {REQUEST_ID: str(uuid.uuid4())}
1895
+
1896
+ return self.rest.request(
1897
+ "/queries/v1/abort-request?" + urlencode(url_parameters),
1898
+ {
1899
+ "sqlText": sql,
1900
+ REQUEST_ID: str(request_id),
1901
+ },
1902
+ )
1903
+
1904
+ def _next_sequence_counter(self) -> int:
1905
+ """Gets next sequence counter. Used internally."""
1906
+ with self._lock_sequence_counter:
1907
+ self.sequence_counter += 1
1908
+ logger.debug("sequence counter: %s", self.sequence_counter)
1909
+ return self.sequence_counter
1910
+
1911
+ def _log_telemetry(self, telemetry_data) -> None:
1912
+ """Logs data to telemetry."""
1913
+ if self.telemetry_enabled:
1914
+ self._telemetry.try_add_log_to_batch(telemetry_data)
1915
+
1916
+ def _add_heartbeat(self) -> None:
1917
+ """Add a periodic heartbeat query in order to keep connection alive."""
1918
+ if not self.heartbeat_thread:
1919
+ self._validate_client_session_keep_alive_heartbeat_frequency()
1920
+ heartbeat_wref = weakref.WeakMethod(self._heartbeat_tick)
1921
+
1922
+ def beat_if_possible() -> None:
1923
+ heartbeat_fn = heartbeat_wref()
1924
+ if heartbeat_fn:
1925
+ heartbeat_fn()
1926
+
1927
+ self.heartbeat_thread = HeartBeatTimer(
1928
+ self.client_session_keep_alive_heartbeat_frequency,
1929
+ beat_if_possible,
1930
+ )
1931
+ self.heartbeat_thread.start()
1932
+ logger.debug("started heartbeat")
1933
+
1934
+ def _cancel_heartbeat(self) -> None:
1935
+ """Cancel a heartbeat thread."""
1936
+ if self.heartbeat_thread:
1937
+ self.heartbeat_thread.cancel()
1938
+ self.heartbeat_thread.join()
1939
+ self.heartbeat_thread = None
1940
+ logger.debug("stopped heartbeat")
1941
+
1942
+ def _heartbeat_tick(self) -> None:
1943
+ """Execute a heartbeat if connection isn't closed yet."""
1944
+ if not self.is_closed():
1945
+ logger.debug("heartbeating!")
1946
+ self.rest._heartbeat()
1947
+
1948
+ def _validate_client_session_keep_alive_heartbeat_frequency(self) -> int:
1949
+ """Validate and return heartbeat frequency in seconds."""
1950
+ real_max = int(self.rest.master_validity_in_seconds / 4)
1951
+ real_min = int(real_max / 4)
1952
+
1953
+ # ensure the type is integer
1954
+ self._client_session_keep_alive_heartbeat_frequency = int(
1955
+ self.client_session_keep_alive_heartbeat_frequency
1956
+ )
1957
+
1958
+ if self.client_session_keep_alive_heartbeat_frequency is None:
1959
+ # This is an unlikely scenario but covering it just in case.
1960
+ self._client_session_keep_alive_heartbeat_frequency = real_min
1961
+ elif self.client_session_keep_alive_heartbeat_frequency > real_max:
1962
+ self._client_session_keep_alive_heartbeat_frequency = real_max
1963
+ elif self.client_session_keep_alive_heartbeat_frequency < real_min:
1964
+ self._client_session_keep_alive_heartbeat_frequency = real_min
1965
+
1966
+ return self.client_session_keep_alive_heartbeat_frequency
1967
+
1968
+ def _validate_client_prefetch_threads(self) -> int:
1969
+ if self.client_prefetch_threads <= 0:
1970
+ self._client_prefetch_threads = 1
1971
+ elif self.client_prefetch_threads > MAX_CLIENT_PREFETCH_THREADS:
1972
+ self._client_prefetch_threads = MAX_CLIENT_PREFETCH_THREADS
1973
+ self._client_prefetch_threads = int(self.client_prefetch_threads)
1974
+ return self.client_prefetch_threads
1975
+
1976
+ def _update_parameters(
1977
+ self,
1978
+ parameters: dict[str, str | int | bool],
1979
+ ) -> None:
1980
+ """Update session parameters."""
1981
+ with self._lock_converter:
1982
+ self.converter.set_parameters(parameters)
1983
+ for name, value in parameters.items():
1984
+ self._session_parameters[name] = value
1985
+ if PARAMETER_CLIENT_TELEMETRY_ENABLED == name:
1986
+ self._server_param_telemetry_enabled = value
1987
+ elif PARAMETER_CLIENT_SESSION_KEEP_ALIVE == name:
1988
+ # Only set if the local config is None.
1989
+ # Always give preference to user config.
1990
+ if self.client_session_keep_alive is None:
1991
+ self.client_session_keep_alive = value
1992
+ elif (
1993
+ PARAMETER_CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY == name
1994
+ and self.client_session_keep_alive_heartbeat_frequency is None
1995
+ ):
1996
+ # Only set if local value hasn't been set already.
1997
+ self.client_session_keep_alive_heartbeat_frequency = value
1998
+ elif PARAMETER_SERVICE_NAME == name:
1999
+ self.service_name = value
2000
+ elif PARAMETER_CLIENT_PREFETCH_THREADS == name:
2001
+ self.client_prefetch_threads = value
2002
+ elif PARAMETER_ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1 == name:
2003
+ self.enable_stage_s3_privatelink_for_us_east_1 = value
2004
+ elif PARAMETER_QUERY_CONTEXT_CACHE_SIZE == name:
2005
+ self.query_context_cache_size = value
2006
+
2007
+ def _format_query_for_log(self, query: str) -> str:
2008
+ ret = " ".join(line.strip() for line in query.split("\n"))
2009
+ return (
2010
+ ret
2011
+ if len(ret) < self.log_max_query_length
2012
+ else ret[0 : self.log_max_query_length] + "..."
2013
+ )
2014
+
2015
+ def __enter__(self) -> SnowflakeConnection:
2016
+ """Context manager."""
2017
+ return self
2018
+
2019
+ def __exit__(
2020
+ self,
2021
+ exc_type: type[BaseException] | None,
2022
+ exc_val: BaseException | None,
2023
+ exc_tb: TracebackType | None,
2024
+ ) -> None:
2025
+ """Context manager with commit or rollback teardown."""
2026
+ if not self._session_parameters.get("AUTOCOMMIT", False):
2027
+ # Either AUTOCOMMIT is turned off, or is not set so we default to old behavior
2028
+ if exc_tb is None:
2029
+ self.commit()
2030
+ else:
2031
+ self.rollback()
2032
+ self.close()
2033
+
2034
+ def _get_query_status(self, sf_qid: str) -> tuple[QueryStatus, dict[str, Any]]:
2035
+ """Retrieves the status of query with sf_qid and returns it with the raw response.
2036
+
2037
+ This is the underlying function used by the public get_status functions.
2038
+
2039
+ Args:
2040
+ sf_qid: Snowflake query id of interest.
2041
+
2042
+ Raises:
2043
+ ValueError: if sf_qid is not a valid UUID string.
2044
+ """
2045
+ try:
2046
+ uuid.UUID(sf_qid)
2047
+ except ValueError:
2048
+ raise ValueError(f"Invalid UUID: '{sf_qid}'")
2049
+ logger.debug(f"get_query_status sf_qid='{sf_qid}'")
2050
+
2051
+ status = "NO_DATA"
2052
+ if self.is_closed():
2053
+ return QueryStatus.DISCONNECTED, {"data": {"queries": []}}
2054
+ status_resp = self.rest.request(
2055
+ "/monitoring/queries/" + quote(sf_qid), method="get", client="rest"
2056
+ )
2057
+ if "queries" not in status_resp["data"]:
2058
+ return QueryStatus.FAILED_WITH_ERROR, status_resp
2059
+ queries = status_resp["data"]["queries"]
2060
+ if len(queries) > 0:
2061
+ status = queries[0]["status"]
2062
+ status_ret = QueryStatus[status]
2063
+ return status_ret, status_resp
2064
+
2065
+ def _cache_query_status(self, sf_qid: str, status_ret: QueryStatus) -> None:
2066
+ # If query was started by us and it has finished let's cache this info
2067
+ if sf_qid in self._async_sfqids and not self.is_still_running(status_ret):
2068
+ self._async_sfqids.pop(
2069
+ sf_qid, None
2070
+ ) # Prevent KeyError when multiple threads try to remove the same query id
2071
+ self._done_async_sfqids[sf_qid] = None
2072
+
2073
+ def _close_at_exit(self):
2074
+ with suppress(Exception):
2075
+ self.close(retry=False)
2076
+
2077
+ def _process_error_query_status(
2078
+ self,
2079
+ sf_qid: str,
2080
+ status_resp: dict,
2081
+ error_message: str = "",
2082
+ error_cls: type[Exception] = ProgrammingError,
2083
+ ) -> None:
2084
+ status_resp = status_resp or {}
2085
+ data = status_resp.get("data", {})
2086
+ queries = data.get("queries")
2087
+
2088
+ if sf_qid in self._async_sfqids:
2089
+ self._async_sfqids.pop(sf_qid, None)
2090
+ message = status_resp.get("message")
2091
+ if message is None:
2092
+ message = ""
2093
+ code = queries[0].get("errorCode", -1) if queries else -1
2094
+ sql_state = None
2095
+ if "data" in status_resp:
2096
+ message += queries[0].get("errorMessage", "") if queries else ""
2097
+ sql_state = data.get("sqlState")
2098
+ Error.errorhandler_wrapper(
2099
+ self,
2100
+ None,
2101
+ error_cls,
2102
+ {
2103
+ "msg": message or error_message,
2104
+ "errno": int(code),
2105
+ "sqlstate": sql_state,
2106
+ "sfqid": sf_qid,
2107
+ },
2108
+ )
2109
+
2110
+ def get_query_status(self, sf_qid: str) -> QueryStatus:
2111
+ """Retrieves the status of query with sf_qid.
2112
+
2113
+ Query status is returned as a QueryStatus.
2114
+
2115
+ Args:
2116
+ sf_qid: Snowflake query id of interest.
2117
+
2118
+ Raises:
2119
+ ValueError: if sf_qid is not a valid UUID string.
2120
+ """
2121
+ status, _ = self._get_query_status(sf_qid)
2122
+ self._cache_query_status(sf_qid, status)
2123
+ return status
2124
+
2125
+ def get_query_status_throw_if_error(self, sf_qid: str) -> QueryStatus:
2126
+ """Retrieves the status of query with sf_qid as a QueryStatus and raises an exception if the query terminated with an error.
2127
+
2128
+ Query status is returned as a QueryStatus.
2129
+
2130
+ Args:
2131
+ sf_qid: Snowflake query id of interest.
2132
+
2133
+ Raises:
2134
+ ValueError: if sf_qid is not a valid UUID string.
2135
+ """
2136
+ status, status_resp = self._get_query_status(sf_qid)
2137
+ self._cache_query_status(sf_qid, status)
2138
+ if self.is_an_error(status):
2139
+ self._process_error_query_status(sf_qid, status_resp)
2140
+ return status
2141
+
2142
+ def initialize_query_context_cache(self) -> None:
2143
+ if not self.is_query_context_cache_disabled:
2144
+ self.query_context_cache = QueryContextCache(self.query_context_cache_size)
2145
+
2146
+ def get_query_context(self) -> dict | None:
2147
+ if self.is_query_context_cache_disabled:
2148
+ return None
2149
+ return self.query_context_cache.serialize_to_dict()
2150
+
2151
+ def set_query_context(self, data: dict) -> None:
2152
+ if not self.is_query_context_cache_disabled:
2153
+ self.query_context_cache.deserialize_json_dict(data)
2154
+
2155
+ @staticmethod
2156
+ def is_still_running(status: QueryStatus) -> bool:
2157
+ """Checks whether given status is currently running."""
2158
+ return status in (
2159
+ QueryStatus.RUNNING,
2160
+ QueryStatus.QUEUED,
2161
+ QueryStatus.RESUMING_WAREHOUSE,
2162
+ QueryStatus.QUEUED_REPARING_WAREHOUSE,
2163
+ QueryStatus.BLOCKED,
2164
+ QueryStatus.NO_DATA,
2165
+ )
2166
+
2167
+ @staticmethod
2168
+ def is_an_error(status: QueryStatus) -> bool:
2169
+ """Checks whether given status means that there has been an error."""
2170
+ return status in (
2171
+ QueryStatus.ABORTING,
2172
+ QueryStatus.FAILED_WITH_ERROR,
2173
+ QueryStatus.ABORTED,
2174
+ QueryStatus.FAILED_WITH_INCIDENT,
2175
+ QueryStatus.DISCONNECTED,
2176
+ )
2177
+
2178
+ def _all_async_queries_finished(self) -> bool:
2179
+ """Checks whether all async queries started by this Connection have finished executing."""
2180
+
2181
+ if not self._async_sfqids:
2182
+ return True
2183
+
2184
+ queries = list(reversed(self._async_sfqids.keys()))
2185
+
2186
+ num_workers = min(self.client_prefetch_threads, len(queries))
2187
+ found_unfinished_query = False
2188
+
2189
+ def async_query_check_helper(
2190
+ sfq_id: str,
2191
+ ) -> bool:
2192
+ nonlocal found_unfinished_query
2193
+ return found_unfinished_query or self.is_still_running(
2194
+ self.get_query_status(sfq_id)
2195
+ )
2196
+
2197
+ with ThreadPoolExecutor(
2198
+ max_workers=num_workers, thread_name_prefix="async_query_check_"
2199
+ ) as tpe: # We should upgrade to using cancel_futures=True once supporting 3.9+
2200
+ futures = (tpe.submit(async_query_check_helper, sfqid) for sfqid in queries)
2201
+ for f in as_completed(futures):
2202
+ if f.result():
2203
+ found_unfinished_query = True
2204
+ break
2205
+ for f in futures:
2206
+ f.cancel()
2207
+
2208
+ return not found_unfinished_query
2209
+
2210
+ def _log_telemetry_imported_packages(self) -> None:
2211
+ if self._log_imported_packages_in_telemetry:
2212
+ # filter out duplicates caused by submodules
2213
+ # and internal modules with names starting with an underscore
2214
+ imported_modules = {
2215
+ k.split(".", maxsplit=1)[0]
2216
+ for k in list(sys.modules)
2217
+ if not k.startswith("_")
2218
+ }
2219
+ ts = get_time_millis()
2220
+ self._log_telemetry(
2221
+ TelemetryData.from_telemetry_data_dict(
2222
+ from_dict={
2223
+ TelemetryField.KEY_TYPE.value: TelemetryField.IMPORTED_PACKAGES.value,
2224
+ TelemetryField.KEY_VALUE.value: str(imported_modules),
2225
+ },
2226
+ timestamp=ts,
2227
+ connection=self,
2228
+ )
2229
+ )
2230
+
2231
+ def is_valid(self) -> bool:
2232
+ """This function tries to answer the question: Is this connection still good for sending queries?
2233
+ Attempts to validate the connections both on the TCP/IP and Session levels."""
2234
+ logger.debug("validating connection and session")
2235
+ if self.is_closed():
2236
+ logger.debug("connection is already closed and not valid")
2237
+ return False
2238
+
2239
+ try:
2240
+ logger.debug("trying to heartbeat into the session to validate")
2241
+ hb_result = self.rest._heartbeat()
2242
+ session_valid = hb_result.get("success")
2243
+ logger.debug("session still valid? %s", session_valid)
2244
+ return bool(session_valid)
2245
+ except Exception as e:
2246
+ logger.debug("session could not be validated due to exception: %s", e)
2247
+ return False
2248
+
2249
+ def _check_experimental_authentication_flag(self) -> None:
2250
+ if os.getenv(ENV_VAR_EXPERIMENTAL_AUTHENTICATION, "false").lower() != "true":
2251
+ Error.errorhandler_wrapper(
2252
+ self,
2253
+ None,
2254
+ ProgrammingError,
2255
+ {
2256
+ "msg": f"Please set the '{ENV_VAR_EXPERIMENTAL_AUTHENTICATION}' environment variable true to use the '{self._authenticator}' authenticator.",
2257
+ "errno": ER_EXPERIMENTAL_AUTHENTICATION_NOT_SUPPORTED,
2258
+ },
2259
+ )
2260
+
2261
+ @staticmethod
2262
+ def _detect_application() -> None | str:
2263
+ if ENV_VAR_PARTNER in os.environ.keys():
2264
+ return os.environ[ENV_VAR_PARTNER]
2265
+ if "streamlit" in sys.modules:
2266
+ return "streamlit"
2267
+ if all(
2268
+ (jpmod in sys.modules)
2269
+ for jpmod in ("ipykernel", "jupyter_core", "jupyter_client")
2270
+ ):
2271
+ return "jupyter_notebook"
2272
+ if "snowbooks" in sys.modules:
2273
+ return "snowflake_notebook"