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.
- snowflake/connector/__init__.py +96 -0
- snowflake/connector/_query_context_cache.py +276 -0
- snowflake/connector/_sql_util.py +48 -0
- snowflake/connector/_utils.py +70 -0
- snowflake/connector/arrow_context.py +203 -0
- snowflake/connector/auth/__init__.py +53 -0
- snowflake/connector/auth/_auth.py +586 -0
- snowflake/connector/auth/_http_server.py +220 -0
- snowflake/connector/auth/_oauth_base.py +400 -0
- snowflake/connector/auth/by_plugin.py +219 -0
- snowflake/connector/auth/default.py +36 -0
- snowflake/connector/auth/idtoken.py +72 -0
- snowflake/connector/auth/keypair.py +222 -0
- snowflake/connector/auth/no_auth.py +39 -0
- snowflake/connector/auth/oauth.py +49 -0
- snowflake/connector/auth/oauth_code.py +479 -0
- snowflake/connector/auth/oauth_credentials.py +66 -0
- snowflake/connector/auth/okta.py +335 -0
- snowflake/connector/auth/pat.py +39 -0
- snowflake/connector/auth/usrpwdmfa.py +69 -0
- snowflake/connector/auth/webbrowser.py +502 -0
- snowflake/connector/auth/workload_identity.py +94 -0
- snowflake/connector/azure_storage_client.py +276 -0
- snowflake/connector/backoff_policies.py +141 -0
- snowflake/connector/bind_upload_agent.py +89 -0
- snowflake/connector/cache.py +696 -0
- snowflake/connector/compat.py +127 -0
- snowflake/connector/config_manager.py +496 -0
- snowflake/connector/connection.py +2273 -0
- snowflake/connector/connection_diagnostic.py +776 -0
- snowflake/connector/constants.py +442 -0
- snowflake/connector/converter.py +785 -0
- snowflake/connector/converter_issue23517.py +87 -0
- snowflake/connector/converter_null.py +14 -0
- snowflake/connector/converter_snowsql.py +205 -0
- snowflake/connector/cursor.py +1951 -0
- snowflake/connector/dbapi.py +53 -0
- snowflake/connector/description.py +19 -0
- snowflake/connector/direct_file_operation_utils.py +88 -0
- snowflake/connector/encryption_util.py +220 -0
- snowflake/connector/errorcode.py +91 -0
- snowflake/connector/errors.py +615 -0
- snowflake/connector/externals_utils/__init__.py +0 -0
- snowflake/connector/externals_utils/externals_setup.py +27 -0
- snowflake/connector/feature.py +4 -0
- snowflake/connector/file_compression_type.py +118 -0
- snowflake/connector/file_lock.py +72 -0
- snowflake/connector/file_transfer_agent.py +1215 -0
- snowflake/connector/file_util.py +153 -0
- snowflake/connector/gcs_storage_client.py +474 -0
- snowflake/connector/gzip_decoder.py +85 -0
- snowflake/connector/local_storage_client.py +90 -0
- snowflake/connector/log_configuration.py +60 -0
- snowflake/connector/logging_utils/__init__.py +0 -0
- snowflake/connector/logging_utils/filters.py +72 -0
- snowflake/connector/nanoarrow_arrow_iterator.cp313-win_amd64.pyd +0 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.cpp +60 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.cpp +21 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.hpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.cpp +557 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.hpp +98 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.cpp +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.hpp +115 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.cpp +1005 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.hpp +127 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.cpp +47 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.hpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.cpp +83 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.cpp +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.hpp +72 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.cpp +73 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.hpp +28 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.cpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IColumnConverter.hpp +17 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.cpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.hpp +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.cpp +71 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.hpp +56 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/LICENSE.txt +209 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.cpp +75 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.hpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.cpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.cpp +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.hpp +95 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.cpp +57 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.hpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.cpp +34 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.hpp +48 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.cpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.hpp +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.cpp +346 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.hpp +145 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/macros.hpp +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.cpp +65 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.hpp +68 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_accessors.h +101 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_alloc.h +127 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_assert.h +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_builder.h +1908 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_emitter.h +215 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_endian.h +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_epilogue.h +7 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_flatbuffers.h +55 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_identifier.h +148 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_iov.h +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_prologue.h +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_refmap.h +144 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_rtconfig.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_types.h +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_verifier.h +239 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/flatcc_portable.h +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/paligned_alloc.h +210 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pattributes.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_pop.h +20 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_push.h +51 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian.h +206 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian_detect.h +118 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinline.h +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinttypes.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable.h +2 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable_basic.h +25 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstatic_assert.h +67 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdalign.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdint.h +898 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/punaligned.h +190 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pversion.h +6 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pwarnings.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc.c +3204 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.c +3217 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.h +3618 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.hpp +379 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_arrow_iterator.pyx +256 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.c +512 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.h +350 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.c +33273 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.h +397 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.cpp +100 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.hpp +49 -0
- snowflake/connector/network.py +1297 -0
- snowflake/connector/ocsp_asn1crypto.py +447 -0
- snowflake/connector/ocsp_snowflake.py +1883 -0
- snowflake/connector/options.py +131 -0
- snowflake/connector/pandas_tools.py +732 -0
- snowflake/connector/proxy.py +43 -0
- snowflake/connector/py.typed +0 -0
- snowflake/connector/result_batch.py +786 -0
- snowflake/connector/result_set.py +319 -0
- snowflake/connector/s3_storage_client.py +605 -0
- snowflake/connector/secret_detector.py +181 -0
- snowflake/connector/sf_dirs.py +64 -0
- snowflake/connector/sfbinaryformat.py +35 -0
- snowflake/connector/sfdatetime.py +345 -0
- snowflake/connector/snow_logging.py +144 -0
- snowflake/connector/sqlstate.py +9 -0
- snowflake/connector/ssd_internal_keys.py +30 -0
- snowflake/connector/ssl_wrap_socket.py +134 -0
- snowflake/connector/storage_client.py +478 -0
- snowflake/connector/telemetry.py +249 -0
- snowflake/connector/telemetry_oob.py +543 -0
- snowflake/connector/test_util.py +30 -0
- snowflake/connector/time_util.py +159 -0
- snowflake/connector/token_cache.py +402 -0
- snowflake/connector/tool/__init__.py +0 -0
- snowflake/connector/tool/dump_certs.py +57 -0
- snowflake/connector/tool/dump_ocsp_response.py +139 -0
- snowflake/connector/tool/dump_ocsp_response_cache.py +194 -0
- snowflake/connector/tool/probe_connection.py +69 -0
- snowflake/connector/url_util.py +49 -0
- snowflake/connector/util_text.py +301 -0
- snowflake/connector/vendored/__init__.py +3 -0
- snowflake/connector/vendored/requests/LICENSE +175 -0
- snowflake/connector/vendored/requests/__init__.py +169 -0
- snowflake/connector/vendored/requests/__version__.py +14 -0
- snowflake/connector/vendored/requests/_internal_utils.py +50 -0
- snowflake/connector/vendored/requests/adapters.py +537 -0
- snowflake/connector/vendored/requests/api.py +157 -0
- snowflake/connector/vendored/requests/auth.py +315 -0
- snowflake/connector/vendored/requests/certs.py +17 -0
- snowflake/connector/vendored/requests/compat.py +79 -0
- snowflake/connector/vendored/requests/cookies.py +561 -0
- snowflake/connector/vendored/requests/exceptions.py +140 -0
- snowflake/connector/vendored/requests/help.py +134 -0
- snowflake/connector/vendored/requests/hooks.py +33 -0
- snowflake/connector/vendored/requests/models.py +1033 -0
- snowflake/connector/vendored/requests/sessions.py +833 -0
- snowflake/connector/vendored/requests/status_codes.py +128 -0
- snowflake/connector/vendored/requests/structures.py +99 -0
- snowflake/connector/vendored/requests/utils.py +1093 -0
- snowflake/connector/vendored/urllib3/LICENSE.txt +21 -0
- snowflake/connector/vendored/urllib3/__init__.py +85 -0
- snowflake/connector/vendored/urllib3/_collections.py +355 -0
- snowflake/connector/vendored/urllib3/_version.py +2 -0
- snowflake/connector/vendored/urllib3/connection.py +572 -0
- snowflake/connector/vendored/urllib3/connectionpool.py +1137 -0
- snowflake/connector/vendored/urllib3/contrib/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/contrib/_appengine_environ.py +36 -0
- snowflake/connector/vendored/urllib3/contrib/_securetransport/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/contrib/_securetransport/bindings.py +519 -0
- snowflake/connector/vendored/urllib3/contrib/_securetransport/low_level.py +397 -0
- snowflake/connector/vendored/urllib3/contrib/appengine.py +314 -0
- snowflake/connector/vendored/urllib3/contrib/ntlmpool.py +130 -0
- snowflake/connector/vendored/urllib3/contrib/pyopenssl.py +509 -0
- snowflake/connector/vendored/urllib3/contrib/securetransport.py +920 -0
- snowflake/connector/vendored/urllib3/contrib/socks.py +216 -0
- snowflake/connector/vendored/urllib3/exceptions.py +323 -0
- snowflake/connector/vendored/urllib3/fields.py +274 -0
- snowflake/connector/vendored/urllib3/filepost.py +98 -0
- snowflake/connector/vendored/urllib3/packages/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/packages/backports/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/packages/backports/makefile.py +51 -0
- snowflake/connector/vendored/urllib3/packages/backports/weakref_finalize.py +155 -0
- snowflake/connector/vendored/urllib3/packages/six.py +1076 -0
- snowflake/connector/vendored/urllib3/poolmanager.py +540 -0
- snowflake/connector/vendored/urllib3/request.py +191 -0
- snowflake/connector/vendored/urllib3/response.py +885 -0
- snowflake/connector/vendored/urllib3/util/__init__.py +49 -0
- snowflake/connector/vendored/urllib3/util/connection.py +156 -0
- snowflake/connector/vendored/urllib3/util/proxy.py +57 -0
- snowflake/connector/vendored/urllib3/util/queue.py +22 -0
- snowflake/connector/vendored/urllib3/util/request.py +146 -0
- snowflake/connector/vendored/urllib3/util/response.py +107 -0
- snowflake/connector/vendored/urllib3/util/retry.py +620 -0
- snowflake/connector/vendored/urllib3/util/ssl_.py +495 -0
- snowflake/connector/vendored/urllib3/util/ssl_match_hostname.py +159 -0
- snowflake/connector/vendored/urllib3/util/ssltransport.py +221 -0
- snowflake/connector/vendored/urllib3/util/timeout.py +271 -0
- snowflake/connector/vendored/urllib3/util/url.py +435 -0
- snowflake/connector/vendored/urllib3/util/wait.py +152 -0
- snowflake/connector/version.py +3 -0
- snowflake/connector/wif_util.py +407 -0
- snowflake_connector_python-3.16.0.dist-info/METADATA +1475 -0
- snowflake_connector_python-3.16.0.dist-info/RECORD +246 -0
- snowflake_connector_python-3.16.0.dist-info/WHEEL +5 -0
- snowflake_connector_python-3.16.0.dist-info/entry_points.txt +4 -0
- snowflake_connector_python-3.16.0.dist-info/licenses/LICENSE.txt +202 -0
- snowflake_connector_python-3.16.0.dist-info/licenses/NOTICE +8 -0
- snowflake_connector_python-3.16.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1297 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import collections
|
|
5
|
+
import contextlib
|
|
6
|
+
import gzip
|
|
7
|
+
import itertools
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import re
|
|
11
|
+
import time
|
|
12
|
+
import uuid
|
|
13
|
+
from collections import OrderedDict
|
|
14
|
+
from threading import Lock
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
import OpenSSL.SSL
|
|
18
|
+
|
|
19
|
+
from snowflake.connector.secret_detector import SecretDetector
|
|
20
|
+
from snowflake.connector.vendored.requests.models import PreparedRequest
|
|
21
|
+
from snowflake.connector.vendored.urllib3.connectionpool import (
|
|
22
|
+
HTTPConnectionPool,
|
|
23
|
+
HTTPSConnectionPool,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from . import ssl_wrap_socket
|
|
27
|
+
from .compat import (
|
|
28
|
+
BAD_GATEWAY,
|
|
29
|
+
BAD_REQUEST,
|
|
30
|
+
FORBIDDEN,
|
|
31
|
+
GATEWAY_TIMEOUT,
|
|
32
|
+
INTERNAL_SERVER_ERROR,
|
|
33
|
+
METHOD_NOT_ALLOWED,
|
|
34
|
+
OK,
|
|
35
|
+
REQUEST_TIMEOUT,
|
|
36
|
+
SERVICE_UNAVAILABLE,
|
|
37
|
+
TOO_MANY_REQUESTS,
|
|
38
|
+
UNAUTHORIZED,
|
|
39
|
+
BadStatusLine,
|
|
40
|
+
IncompleteRead,
|
|
41
|
+
urlencode,
|
|
42
|
+
urlparse,
|
|
43
|
+
)
|
|
44
|
+
from .constants import (
|
|
45
|
+
_CONNECTIVITY_ERR_MSG,
|
|
46
|
+
_SNOWFLAKE_HOST_SUFFIX_REGEX,
|
|
47
|
+
HTTP_HEADER_ACCEPT,
|
|
48
|
+
HTTP_HEADER_CONTENT_TYPE,
|
|
49
|
+
HTTP_HEADER_SERVICE_NAME,
|
|
50
|
+
HTTP_HEADER_USER_AGENT,
|
|
51
|
+
)
|
|
52
|
+
from .description import (
|
|
53
|
+
CLIENT_NAME,
|
|
54
|
+
CLIENT_VERSION,
|
|
55
|
+
COMPILER,
|
|
56
|
+
IMPLEMENTATION,
|
|
57
|
+
OPERATING_SYSTEM,
|
|
58
|
+
PLATFORM,
|
|
59
|
+
PYTHON_VERSION,
|
|
60
|
+
SNOWFLAKE_CONNECTOR_VERSION,
|
|
61
|
+
)
|
|
62
|
+
from .errorcode import (
|
|
63
|
+
ER_CONNECTION_IS_CLOSED,
|
|
64
|
+
ER_CONNECTION_TIMEOUT,
|
|
65
|
+
ER_FAILED_TO_CONNECT_TO_DB,
|
|
66
|
+
ER_FAILED_TO_RENEW_SESSION,
|
|
67
|
+
ER_FAILED_TO_REQUEST,
|
|
68
|
+
ER_RETRYABLE_CODE,
|
|
69
|
+
)
|
|
70
|
+
from .errors import (
|
|
71
|
+
BadGatewayError,
|
|
72
|
+
BadRequest,
|
|
73
|
+
DatabaseError,
|
|
74
|
+
Error,
|
|
75
|
+
ForbiddenError,
|
|
76
|
+
GatewayTimeoutError,
|
|
77
|
+
InterfaceError,
|
|
78
|
+
InternalServerError,
|
|
79
|
+
MethodNotAllowed,
|
|
80
|
+
OperationalError,
|
|
81
|
+
OtherHTTPRetryableError,
|
|
82
|
+
ProgrammingError,
|
|
83
|
+
RefreshTokenError,
|
|
84
|
+
ServiceUnavailableError,
|
|
85
|
+
TooManyRequests,
|
|
86
|
+
)
|
|
87
|
+
from .sqlstate import (
|
|
88
|
+
SQLSTATE_CONNECTION_NOT_EXISTS,
|
|
89
|
+
SQLSTATE_CONNECTION_REJECTED,
|
|
90
|
+
SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
|
|
91
|
+
)
|
|
92
|
+
from .time_util import (
|
|
93
|
+
DEFAULT_MASTER_VALIDITY_IN_SECONDS,
|
|
94
|
+
TimeoutBackoffCtx,
|
|
95
|
+
get_time_millis,
|
|
96
|
+
)
|
|
97
|
+
from .tool.probe_connection import probe_connection
|
|
98
|
+
from .vendored import requests
|
|
99
|
+
from .vendored.requests import Response, Session
|
|
100
|
+
from .vendored.requests.adapters import HTTPAdapter
|
|
101
|
+
from .vendored.requests.auth import AuthBase
|
|
102
|
+
from .vendored.requests.exceptions import (
|
|
103
|
+
ConnectionError,
|
|
104
|
+
ConnectTimeout,
|
|
105
|
+
InvalidProxyURL,
|
|
106
|
+
ReadTimeout,
|
|
107
|
+
SSLError,
|
|
108
|
+
)
|
|
109
|
+
from .vendored.requests.utils import prepend_scheme_if_needed, select_proxy
|
|
110
|
+
from .vendored.urllib3.exceptions import ProtocolError
|
|
111
|
+
from .vendored.urllib3.poolmanager import ProxyManager
|
|
112
|
+
from .vendored.urllib3.util.url import parse_url
|
|
113
|
+
|
|
114
|
+
if TYPE_CHECKING:
|
|
115
|
+
from .connection import SnowflakeConnection
|
|
116
|
+
logger = logging.getLogger(__name__)
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
Monkey patch for PyOpenSSL Socket wrapper
|
|
120
|
+
"""
|
|
121
|
+
ssl_wrap_socket.inject_into_urllib3()
|
|
122
|
+
|
|
123
|
+
# known applications
|
|
124
|
+
APPLICATION_SNOWSQL = "SnowSQL"
|
|
125
|
+
|
|
126
|
+
# requests parameters
|
|
127
|
+
REQUESTS_RETRY = 1 # requests library builtin retry
|
|
128
|
+
DEFAULT_SOCKET_CONNECT_TIMEOUT = 1 * 60 # don't reduce less than 45 seconds
|
|
129
|
+
|
|
130
|
+
# return codes
|
|
131
|
+
QUERY_IN_PROGRESS_CODE = "333333" # GS code: the query is in progress
|
|
132
|
+
QUERY_IN_PROGRESS_ASYNC_CODE = "333334" # GS code: the query is detached
|
|
133
|
+
|
|
134
|
+
ID_TOKEN_EXPIRED_GS_CODE = "390110"
|
|
135
|
+
SESSION_EXPIRED_GS_CODE = "390112" # GS code: session expired. need to renew
|
|
136
|
+
MASTER_TOKEN_NOTFOUND_GS_CODE = "390113"
|
|
137
|
+
MASTER_TOKEN_EXPIRED_GS_CODE = "390114"
|
|
138
|
+
MASTER_TOKEN_INVALD_GS_CODE = "390115"
|
|
139
|
+
ID_TOKEN_INVALID_LOGIN_REQUEST_GS_CODE = "390195"
|
|
140
|
+
BAD_REQUEST_GS_CODE = "390400"
|
|
141
|
+
OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE = "390318"
|
|
142
|
+
|
|
143
|
+
# other constants
|
|
144
|
+
CONTENT_TYPE_APPLICATION_JSON = "application/json"
|
|
145
|
+
ACCEPT_TYPE_APPLICATION_SNOWFLAKE = "application/snowflake"
|
|
146
|
+
|
|
147
|
+
REQUEST_TYPE_RENEW = "RENEW"
|
|
148
|
+
|
|
149
|
+
HEADER_AUTHORIZATION_KEY = "Authorization"
|
|
150
|
+
HEADER_SNOWFLAKE_TOKEN = 'Snowflake Token="{token}"'
|
|
151
|
+
HEADER_EXTERNAL_SESSION_KEY = "X-Snowflake-External-Session-ID"
|
|
152
|
+
|
|
153
|
+
REQUEST_ID = "requestId"
|
|
154
|
+
REQUEST_GUID = "request_guid"
|
|
155
|
+
SNOWFLAKE_HOST_SUFFIX = ".snowflakecomputing.com"
|
|
156
|
+
|
|
157
|
+
SNOWFLAKE_CONNECTOR_VERSION = SNOWFLAKE_CONNECTOR_VERSION
|
|
158
|
+
PYTHON_VERSION = PYTHON_VERSION
|
|
159
|
+
OPERATING_SYSTEM = OPERATING_SYSTEM
|
|
160
|
+
PLATFORM = PLATFORM
|
|
161
|
+
IMPLEMENTATION = IMPLEMENTATION
|
|
162
|
+
COMPILER = COMPILER
|
|
163
|
+
|
|
164
|
+
CLIENT_NAME = CLIENT_NAME # don't change!
|
|
165
|
+
CLIENT_VERSION = CLIENT_VERSION
|
|
166
|
+
PYTHON_CONNECTOR_USER_AGENT = f"{CLIENT_NAME}/{SNOWFLAKE_CONNECTOR_VERSION} ({PLATFORM}) {IMPLEMENTATION}/{PYTHON_VERSION}"
|
|
167
|
+
|
|
168
|
+
NO_TOKEN = "no-token"
|
|
169
|
+
NO_EXTERNAL_SESSION_ID = "no-external-session-id"
|
|
170
|
+
|
|
171
|
+
STATUS_TO_EXCEPTION: dict[int, type[Error]] = {
|
|
172
|
+
INTERNAL_SERVER_ERROR: InternalServerError,
|
|
173
|
+
FORBIDDEN: ForbiddenError,
|
|
174
|
+
SERVICE_UNAVAILABLE: ServiceUnavailableError,
|
|
175
|
+
GATEWAY_TIMEOUT: GatewayTimeoutError,
|
|
176
|
+
BAD_REQUEST: BadRequest,
|
|
177
|
+
BAD_GATEWAY: BadGatewayError,
|
|
178
|
+
METHOD_NOT_ALLOWED: MethodNotAllowed,
|
|
179
|
+
TOO_MANY_REQUESTS: TooManyRequests,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
DEFAULT_AUTHENTICATOR = "SNOWFLAKE" # default authenticator name
|
|
183
|
+
EXTERNAL_BROWSER_AUTHENTICATOR = "EXTERNALBROWSER"
|
|
184
|
+
KEY_PAIR_AUTHENTICATOR = "SNOWFLAKE_JWT"
|
|
185
|
+
OAUTH_AUTHENTICATOR = "OAUTH"
|
|
186
|
+
OAUTH_AUTHORIZATION_CODE = "OAUTH_AUTHORIZATION_CODE"
|
|
187
|
+
OAUTH_CLIENT_CREDENTIALS = "OAUTH_CLIENT_CREDENTIALS"
|
|
188
|
+
ID_TOKEN_AUTHENTICATOR = "ID_TOKEN"
|
|
189
|
+
USR_PWD_MFA_AUTHENTICATOR = "USERNAME_PASSWORD_MFA"
|
|
190
|
+
PROGRAMMATIC_ACCESS_TOKEN = "PROGRAMMATIC_ACCESS_TOKEN"
|
|
191
|
+
NO_AUTH_AUTHENTICATOR = "NO_AUTH"
|
|
192
|
+
WORKLOAD_IDENTITY_AUTHENTICATOR = "WORKLOAD_IDENTITY"
|
|
193
|
+
PAT_WITH_EXTERNAL_SESSION = "PAT_WITH_EXTERNAL_SESSION"
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def is_retryable_http_code(code: int) -> bool:
|
|
197
|
+
"""Decides whether code is a retryable HTTP issue."""
|
|
198
|
+
return 500 <= code < 600 or code in (
|
|
199
|
+
BAD_REQUEST, # 400
|
|
200
|
+
FORBIDDEN, # 403
|
|
201
|
+
METHOD_NOT_ALLOWED, # 405
|
|
202
|
+
REQUEST_TIMEOUT, # 408
|
|
203
|
+
TOO_MANY_REQUESTS, # 429
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def get_http_retryable_error(status_code: int) -> Error:
|
|
208
|
+
error_class: type[Error] = STATUS_TO_EXCEPTION.get(
|
|
209
|
+
status_code, OtherHTTPRetryableError
|
|
210
|
+
)
|
|
211
|
+
return error_class(errno=status_code)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def raise_okta_unauthorized_error(
|
|
215
|
+
connection: SnowflakeConnection | None, response: Response
|
|
216
|
+
) -> None:
|
|
217
|
+
Error.errorhandler_wrapper(
|
|
218
|
+
connection,
|
|
219
|
+
None,
|
|
220
|
+
DatabaseError,
|
|
221
|
+
{
|
|
222
|
+
"msg": f"Failed to get authentication by OKTA: {response.status_code}: {response.reason}",
|
|
223
|
+
"errno": ER_FAILED_TO_CONNECT_TO_DB,
|
|
224
|
+
"sqlstate": SQLSTATE_CONNECTION_REJECTED,
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def raise_failed_request_error(
|
|
230
|
+
connection: SnowflakeConnection | None,
|
|
231
|
+
url: str,
|
|
232
|
+
method: str,
|
|
233
|
+
response: Response,
|
|
234
|
+
) -> None:
|
|
235
|
+
Error.errorhandler_wrapper(
|
|
236
|
+
connection,
|
|
237
|
+
None,
|
|
238
|
+
InterfaceError,
|
|
239
|
+
{
|
|
240
|
+
"msg": f"{response.status_code} {response.reason}: {method} {url}",
|
|
241
|
+
"errno": ER_FAILED_TO_REQUEST,
|
|
242
|
+
"sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
|
|
243
|
+
},
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def is_login_request(url: str) -> bool:
|
|
248
|
+
return "login-request" in parse_url(url).path
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class ProxySupportAdapter(HTTPAdapter):
|
|
252
|
+
"""This Adapter creates proper headers for Proxy CONNECT messages."""
|
|
253
|
+
|
|
254
|
+
def get_connection(
|
|
255
|
+
self, url: str, proxies: OrderedDict | None = None
|
|
256
|
+
) -> HTTPConnectionPool | HTTPSConnectionPool:
|
|
257
|
+
proxy = select_proxy(url, proxies)
|
|
258
|
+
parsed_url = urlparse(url)
|
|
259
|
+
|
|
260
|
+
if proxy:
|
|
261
|
+
proxy = prepend_scheme_if_needed(proxy, "http")
|
|
262
|
+
proxy_url = parse_url(proxy)
|
|
263
|
+
if not proxy_url.host:
|
|
264
|
+
raise InvalidProxyURL(
|
|
265
|
+
"Please check proxy URL. It is malformed"
|
|
266
|
+
" and could be missing the host."
|
|
267
|
+
)
|
|
268
|
+
proxy_manager = self.proxy_manager_for(proxy)
|
|
269
|
+
|
|
270
|
+
if isinstance(proxy_manager, ProxyManager):
|
|
271
|
+
# Add Host to proxy header SNOW-232777
|
|
272
|
+
proxy_manager.proxy_headers["Host"] = parsed_url.hostname
|
|
273
|
+
else:
|
|
274
|
+
logger.debug(
|
|
275
|
+
f"Unable to set 'Host' to proxy manager of type {type(proxy_manager)} as"
|
|
276
|
+
f" it does not have attribute 'proxy_headers'."
|
|
277
|
+
)
|
|
278
|
+
conn = proxy_manager.connection_from_url(url)
|
|
279
|
+
else:
|
|
280
|
+
# Only scheme should be lower case
|
|
281
|
+
url = parsed_url.geturl()
|
|
282
|
+
conn = self.poolmanager.connection_from_url(url)
|
|
283
|
+
|
|
284
|
+
return conn
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class RetryRequest(Exception):
|
|
288
|
+
"""Signal to retry request."""
|
|
289
|
+
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class ReauthenticationRequest(Exception):
|
|
294
|
+
"""Signal to reauthenticate."""
|
|
295
|
+
|
|
296
|
+
def __init__(self, cause) -> None:
|
|
297
|
+
self.cause = cause
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class SnowflakeAuth(AuthBase):
|
|
301
|
+
"""Attaches HTTP Authorization header for Snowflake."""
|
|
302
|
+
|
|
303
|
+
def __init__(self, token) -> None:
|
|
304
|
+
# setup any auth-related data here
|
|
305
|
+
self.token = token
|
|
306
|
+
|
|
307
|
+
def __call__(self, r: PreparedRequest) -> PreparedRequest:
|
|
308
|
+
"""Modifies and returns the request."""
|
|
309
|
+
if HEADER_AUTHORIZATION_KEY in r.headers:
|
|
310
|
+
del r.headers[HEADER_AUTHORIZATION_KEY]
|
|
311
|
+
if self.token != NO_TOKEN:
|
|
312
|
+
r.headers[HEADER_AUTHORIZATION_KEY] = HEADER_SNOWFLAKE_TOKEN.format(
|
|
313
|
+
token=self.token
|
|
314
|
+
)
|
|
315
|
+
return r
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class PATWithExternalSessionAuth(AuthBase):
|
|
319
|
+
"""Attaches HTTP Authorization headers for PAT with External Session."""
|
|
320
|
+
|
|
321
|
+
def __init__(self, token, external_session_id) -> None:
|
|
322
|
+
# setup any auth-related data here
|
|
323
|
+
self.token = token
|
|
324
|
+
self.external_session_id = external_session_id
|
|
325
|
+
|
|
326
|
+
def __call__(self, r: PreparedRequest) -> PreparedRequest:
|
|
327
|
+
"""Modifies and returns the request."""
|
|
328
|
+
if HEADER_AUTHORIZATION_KEY in r.headers:
|
|
329
|
+
del r.headers[HEADER_AUTHORIZATION_KEY]
|
|
330
|
+
if self.token != NO_TOKEN:
|
|
331
|
+
r.headers[HEADER_AUTHORIZATION_KEY] = "Bearer " + self.token
|
|
332
|
+
if self.external_session_id != NO_EXTERNAL_SESSION_ID:
|
|
333
|
+
r.headers[HEADER_EXTERNAL_SESSION_KEY] = self.external_session_id
|
|
334
|
+
return r
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class SessionPool:
|
|
338
|
+
def __init__(self, rest: SnowflakeRestful) -> None:
|
|
339
|
+
# A stack of the idle sessions
|
|
340
|
+
self._idle_sessions: list[Session] = []
|
|
341
|
+
self._active_sessions: set[Session] = set()
|
|
342
|
+
self._rest: SnowflakeRestful = rest
|
|
343
|
+
|
|
344
|
+
def get_session(self) -> Session:
|
|
345
|
+
"""Returns a session from the session pool or creates a new one."""
|
|
346
|
+
try:
|
|
347
|
+
session = self._idle_sessions.pop()
|
|
348
|
+
except IndexError:
|
|
349
|
+
session = self._rest.make_requests_session()
|
|
350
|
+
self._active_sessions.add(session)
|
|
351
|
+
return session
|
|
352
|
+
|
|
353
|
+
def return_session(self, session: Session) -> None:
|
|
354
|
+
"""Places an active session back into the idle session stack."""
|
|
355
|
+
try:
|
|
356
|
+
self._active_sessions.remove(session)
|
|
357
|
+
except KeyError:
|
|
358
|
+
logger.debug("session doesn't exist in the active session pool. Ignored...")
|
|
359
|
+
self._idle_sessions.append(session)
|
|
360
|
+
|
|
361
|
+
def __str__(self) -> str:
|
|
362
|
+
total_sessions = len(self._active_sessions) + len(self._idle_sessions)
|
|
363
|
+
return (
|
|
364
|
+
f"SessionPool {len(self._active_sessions)}/{total_sessions} active sessions"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def close(self) -> None:
|
|
368
|
+
"""Closes all active and idle sessions in this session pool."""
|
|
369
|
+
if self._active_sessions:
|
|
370
|
+
logger.debug(f"Closing {len(self._active_sessions)} active sessions")
|
|
371
|
+
for s in itertools.chain(self._active_sessions, self._idle_sessions):
|
|
372
|
+
try:
|
|
373
|
+
s.close()
|
|
374
|
+
except Exception as e:
|
|
375
|
+
logger.info(f"Session cleanup failed: {e}")
|
|
376
|
+
self._active_sessions.clear()
|
|
377
|
+
self._idle_sessions.clear()
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
# Customizable JSONEncoder to support additional types.
|
|
381
|
+
class SnowflakeRestfulJsonEncoder(json.JSONEncoder):
|
|
382
|
+
def default(self, o):
|
|
383
|
+
if isinstance(o, uuid.UUID):
|
|
384
|
+
return str(o)
|
|
385
|
+
|
|
386
|
+
return super().default(o)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class SnowflakeRestful:
|
|
390
|
+
"""Snowflake Restful class."""
|
|
391
|
+
|
|
392
|
+
def __init__(
|
|
393
|
+
self,
|
|
394
|
+
host: str = "127.0.0.1",
|
|
395
|
+
port: int = 8080,
|
|
396
|
+
protocol: str = "http",
|
|
397
|
+
inject_client_pause: int = 0,
|
|
398
|
+
connection: SnowflakeConnection | None = None,
|
|
399
|
+
) -> None:
|
|
400
|
+
self._host = host
|
|
401
|
+
self._port = port
|
|
402
|
+
self._protocol = protocol
|
|
403
|
+
self._inject_client_pause = inject_client_pause
|
|
404
|
+
self._connection = connection
|
|
405
|
+
self._lock_token = Lock()
|
|
406
|
+
self._sessions_map: dict[str | None, SessionPool] = collections.defaultdict(
|
|
407
|
+
lambda: SessionPool(self)
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# OCSP mode (OCSPMode.FAIL_OPEN by default)
|
|
411
|
+
ssl_wrap_socket.FEATURE_OCSP_MODE = (
|
|
412
|
+
self._connection._ocsp_mode()
|
|
413
|
+
if self._connection
|
|
414
|
+
else ssl_wrap_socket.DEFAULT_OCSP_MODE
|
|
415
|
+
)
|
|
416
|
+
# cache file name (enabled by default)
|
|
417
|
+
ssl_wrap_socket.FEATURE_OCSP_RESPONSE_CACHE_FILE_NAME = (
|
|
418
|
+
self._connection._ocsp_response_cache_filename if self._connection else None
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
# This is to address the issue where requests hangs
|
|
422
|
+
_ = "dummy".encode("idna").decode("utf-8")
|
|
423
|
+
|
|
424
|
+
@property
|
|
425
|
+
def token(self) -> str | None:
|
|
426
|
+
return self._token if hasattr(self, "_token") else None
|
|
427
|
+
|
|
428
|
+
@property
|
|
429
|
+
def external_session_id(self) -> str | None:
|
|
430
|
+
return (
|
|
431
|
+
self._external_session_id if hasattr(self, "_external_session_id") else None
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def master_token(self) -> str | None:
|
|
436
|
+
return self._master_token if hasattr(self, "_master_token") else None
|
|
437
|
+
|
|
438
|
+
@property
|
|
439
|
+
def master_validity_in_seconds(self) -> int:
|
|
440
|
+
return (
|
|
441
|
+
self._master_validity_in_seconds
|
|
442
|
+
if hasattr(self, "_master_validity_in_seconds")
|
|
443
|
+
and self._master_validity_in_seconds
|
|
444
|
+
else DEFAULT_MASTER_VALIDITY_IN_SECONDS
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
@master_validity_in_seconds.setter
|
|
448
|
+
def master_validity_in_seconds(self, value) -> None:
|
|
449
|
+
self._master_validity_in_seconds = (
|
|
450
|
+
value if value else DEFAULT_MASTER_VALIDITY_IN_SECONDS
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
@property
|
|
454
|
+
def id_token(self):
|
|
455
|
+
return getattr(self, "_id_token", None)
|
|
456
|
+
|
|
457
|
+
@id_token.setter
|
|
458
|
+
def id_token(self, value) -> None:
|
|
459
|
+
self._id_token = value
|
|
460
|
+
|
|
461
|
+
@property
|
|
462
|
+
def mfa_token(self) -> str | None:
|
|
463
|
+
return getattr(self, "_mfa_token", None)
|
|
464
|
+
|
|
465
|
+
@mfa_token.setter
|
|
466
|
+
def mfa_token(self, value: str) -> None:
|
|
467
|
+
self._mfa_token = value
|
|
468
|
+
|
|
469
|
+
@property
|
|
470
|
+
def server_url(self) -> str:
|
|
471
|
+
return f"{self._protocol}://{self._host}:{self._port}"
|
|
472
|
+
|
|
473
|
+
def close(self) -> None:
|
|
474
|
+
if hasattr(self, "_token"):
|
|
475
|
+
del self._token
|
|
476
|
+
if hasattr(self, "_master_token"):
|
|
477
|
+
del self._master_token
|
|
478
|
+
if hasattr(self, "_id_token"):
|
|
479
|
+
del self._id_token
|
|
480
|
+
if hasattr(self, "_mfa_token"):
|
|
481
|
+
del self._mfa_token
|
|
482
|
+
|
|
483
|
+
for session_pool in self._sessions_map.values():
|
|
484
|
+
session_pool.close()
|
|
485
|
+
|
|
486
|
+
def request(
|
|
487
|
+
self,
|
|
488
|
+
url,
|
|
489
|
+
body=None,
|
|
490
|
+
method: str = "post",
|
|
491
|
+
client: str = "sfsql",
|
|
492
|
+
timeout: int | None = None,
|
|
493
|
+
_no_results: bool = False,
|
|
494
|
+
_include_retry_params: bool = False,
|
|
495
|
+
_no_retry: bool = False,
|
|
496
|
+
):
|
|
497
|
+
if body is None:
|
|
498
|
+
body = {}
|
|
499
|
+
if self.master_token is None and self.token is None:
|
|
500
|
+
Error.errorhandler_wrapper(
|
|
501
|
+
self._connection,
|
|
502
|
+
None,
|
|
503
|
+
DatabaseError,
|
|
504
|
+
{
|
|
505
|
+
"msg": "Connection is closed",
|
|
506
|
+
"errno": ER_CONNECTION_IS_CLOSED,
|
|
507
|
+
"sqlstate": SQLSTATE_CONNECTION_NOT_EXISTS,
|
|
508
|
+
},
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
if client == "sfsql":
|
|
512
|
+
accept_type = ACCEPT_TYPE_APPLICATION_SNOWFLAKE
|
|
513
|
+
else:
|
|
514
|
+
accept_type = CONTENT_TYPE_APPLICATION_JSON
|
|
515
|
+
|
|
516
|
+
headers = {
|
|
517
|
+
HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
|
|
518
|
+
HTTP_HEADER_ACCEPT: accept_type,
|
|
519
|
+
HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
|
|
520
|
+
}
|
|
521
|
+
try:
|
|
522
|
+
# SNOW-1763555: inject OpenTelemetry headers if available specifically in WC3 format
|
|
523
|
+
# into our request headers in case tracing is enabled. This should make sure that
|
|
524
|
+
# our requests are accounted for properly if OpenTelemetry is used by users.
|
|
525
|
+
from opentelemetry.trace.propagation.tracecontext import (
|
|
526
|
+
TraceContextTextMapPropagator,
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
TraceContextTextMapPropagator().inject(headers)
|
|
530
|
+
except Exception:
|
|
531
|
+
logger.debug(
|
|
532
|
+
"Opentelemtry otel injection failed",
|
|
533
|
+
exc_info=True,
|
|
534
|
+
)
|
|
535
|
+
if self._connection.service_name:
|
|
536
|
+
headers[HTTP_HEADER_SERVICE_NAME] = self._connection.service_name
|
|
537
|
+
if method == "post":
|
|
538
|
+
return self._post_request(
|
|
539
|
+
url,
|
|
540
|
+
headers,
|
|
541
|
+
json.dumps(body, cls=SnowflakeRestfulJsonEncoder),
|
|
542
|
+
token=self.token,
|
|
543
|
+
external_session_id=self.external_session_id,
|
|
544
|
+
_no_results=_no_results,
|
|
545
|
+
timeout=timeout,
|
|
546
|
+
_include_retry_params=_include_retry_params,
|
|
547
|
+
no_retry=_no_retry,
|
|
548
|
+
)
|
|
549
|
+
else:
|
|
550
|
+
return self._get_request(
|
|
551
|
+
url,
|
|
552
|
+
headers,
|
|
553
|
+
token=self.token,
|
|
554
|
+
external_session_id=self.external_session_id,
|
|
555
|
+
timeout=timeout,
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
def update_tokens(
|
|
559
|
+
self,
|
|
560
|
+
session_token,
|
|
561
|
+
master_token,
|
|
562
|
+
master_validity_in_seconds=None,
|
|
563
|
+
id_token=None,
|
|
564
|
+
mfa_token=None,
|
|
565
|
+
) -> None:
|
|
566
|
+
"""Updates session and master tokens and optionally temporary credential."""
|
|
567
|
+
with self._lock_token:
|
|
568
|
+
self._token = session_token
|
|
569
|
+
self._master_token = master_token
|
|
570
|
+
self._id_token = id_token
|
|
571
|
+
self._mfa_token = mfa_token
|
|
572
|
+
self._master_validity_in_seconds = master_validity_in_seconds
|
|
573
|
+
|
|
574
|
+
def set_pat_and_external_session(
|
|
575
|
+
self,
|
|
576
|
+
personal_access_token,
|
|
577
|
+
external_session_id,
|
|
578
|
+
) -> None:
|
|
579
|
+
"""Updates session and master tokens and optionally temporary credential."""
|
|
580
|
+
with self._lock_token:
|
|
581
|
+
self._personal_access_token = personal_access_token
|
|
582
|
+
self._token = personal_access_token
|
|
583
|
+
self._external_session_id = external_session_id
|
|
584
|
+
|
|
585
|
+
def _renew_session(self):
|
|
586
|
+
"""Renew a session and master token."""
|
|
587
|
+
return self._token_request(REQUEST_TYPE_RENEW)
|
|
588
|
+
|
|
589
|
+
def _token_request(self, request_type):
|
|
590
|
+
logger.debug(
|
|
591
|
+
"updating session. master_token: {}".format(
|
|
592
|
+
"****" if self.master_token else None
|
|
593
|
+
)
|
|
594
|
+
)
|
|
595
|
+
headers = {
|
|
596
|
+
HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
|
|
597
|
+
HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
|
|
598
|
+
HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
|
|
599
|
+
}
|
|
600
|
+
if self._connection.service_name:
|
|
601
|
+
headers[HTTP_HEADER_SERVICE_NAME] = self._connection.service_name
|
|
602
|
+
request_id = str(uuid.uuid4())
|
|
603
|
+
logger.debug("request_id: %s", request_id)
|
|
604
|
+
url = "/session/token-request?" + urlencode({REQUEST_ID: request_id})
|
|
605
|
+
|
|
606
|
+
# NOTE: ensure an empty key if master token is not set.
|
|
607
|
+
# This avoids HTTP 400.
|
|
608
|
+
header_token = self.master_token or ""
|
|
609
|
+
body = {
|
|
610
|
+
"oldSessionToken": self.token,
|
|
611
|
+
"requestType": request_type,
|
|
612
|
+
}
|
|
613
|
+
ret = self._post_request(
|
|
614
|
+
url,
|
|
615
|
+
headers,
|
|
616
|
+
json.dumps(body, cls=SnowflakeRestfulJsonEncoder),
|
|
617
|
+
token=header_token,
|
|
618
|
+
)
|
|
619
|
+
if ret.get("success") and ret.get("data", {}).get("sessionToken"):
|
|
620
|
+
logger.debug("success: %s", SecretDetector.mask_secrets(str(ret)))
|
|
621
|
+
self.update_tokens(
|
|
622
|
+
ret["data"]["sessionToken"],
|
|
623
|
+
ret["data"].get("masterToken"),
|
|
624
|
+
master_validity_in_seconds=ret["data"].get("masterValidityInSeconds"),
|
|
625
|
+
)
|
|
626
|
+
logger.debug("updating session completed")
|
|
627
|
+
return ret
|
|
628
|
+
else:
|
|
629
|
+
logger.debug("failed: %s", SecretDetector.mask_secrets(str(ret)))
|
|
630
|
+
err = ret.get("message")
|
|
631
|
+
if err is not None and ret.get("data"):
|
|
632
|
+
err += ret["data"].get("errorMessage", "")
|
|
633
|
+
errno = ret.get("code") or ER_FAILED_TO_RENEW_SESSION
|
|
634
|
+
if errno in (
|
|
635
|
+
ID_TOKEN_EXPIRED_GS_CODE,
|
|
636
|
+
SESSION_EXPIRED_GS_CODE,
|
|
637
|
+
MASTER_TOKEN_NOTFOUND_GS_CODE,
|
|
638
|
+
MASTER_TOKEN_EXPIRED_GS_CODE,
|
|
639
|
+
MASTER_TOKEN_INVALD_GS_CODE,
|
|
640
|
+
BAD_REQUEST_GS_CODE,
|
|
641
|
+
):
|
|
642
|
+
raise ReauthenticationRequest(
|
|
643
|
+
ProgrammingError(
|
|
644
|
+
msg=err,
|
|
645
|
+
errno=int(errno),
|
|
646
|
+
sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
|
|
647
|
+
)
|
|
648
|
+
)
|
|
649
|
+
Error.errorhandler_wrapper(
|
|
650
|
+
self._connection,
|
|
651
|
+
None,
|
|
652
|
+
ProgrammingError,
|
|
653
|
+
{
|
|
654
|
+
"msg": err,
|
|
655
|
+
"errno": int(errno),
|
|
656
|
+
"sqlstate": SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED,
|
|
657
|
+
},
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
def _heartbeat(self) -> Any | dict[Any, Any] | None:
|
|
661
|
+
headers = {
|
|
662
|
+
HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
|
|
663
|
+
HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
|
|
664
|
+
HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
|
|
665
|
+
}
|
|
666
|
+
if self._connection.service_name:
|
|
667
|
+
headers[HTTP_HEADER_SERVICE_NAME] = self._connection.service_name
|
|
668
|
+
request_id = str(uuid.uuid4())
|
|
669
|
+
logger.debug("request_id: %s", request_id)
|
|
670
|
+
url = "/session/heartbeat?" + urlencode({REQUEST_ID: request_id})
|
|
671
|
+
ret = self._post_request(
|
|
672
|
+
url,
|
|
673
|
+
headers,
|
|
674
|
+
None,
|
|
675
|
+
token=self.token,
|
|
676
|
+
)
|
|
677
|
+
if not ret.get("success"):
|
|
678
|
+
logger.error("Failed to heartbeat. code: %s, url: %s", ret.get("code"), url)
|
|
679
|
+
return ret
|
|
680
|
+
|
|
681
|
+
def delete_session(self, retry: bool = False) -> None:
|
|
682
|
+
"""Deletes the session."""
|
|
683
|
+
if self.master_token is None:
|
|
684
|
+
Error.errorhandler_wrapper(
|
|
685
|
+
self._connection,
|
|
686
|
+
None,
|
|
687
|
+
DatabaseError,
|
|
688
|
+
{
|
|
689
|
+
"msg": "Connection is closed",
|
|
690
|
+
"errno": ER_CONNECTION_IS_CLOSED,
|
|
691
|
+
"sqlstate": SQLSTATE_CONNECTION_NOT_EXISTS,
|
|
692
|
+
},
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
url = "/session?" + urlencode({"delete": "true"})
|
|
696
|
+
headers = {
|
|
697
|
+
HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_APPLICATION_JSON,
|
|
698
|
+
HTTP_HEADER_ACCEPT: CONTENT_TYPE_APPLICATION_JSON,
|
|
699
|
+
HTTP_HEADER_USER_AGENT: PYTHON_CONNECTOR_USER_AGENT,
|
|
700
|
+
}
|
|
701
|
+
if self._connection.service_name:
|
|
702
|
+
headers[HTTP_HEADER_SERVICE_NAME] = self._connection.service_name
|
|
703
|
+
|
|
704
|
+
body = {}
|
|
705
|
+
retry_limit = 3 if retry else 1
|
|
706
|
+
num_retries = 0
|
|
707
|
+
should_retry = True
|
|
708
|
+
while should_retry and (num_retries < retry_limit):
|
|
709
|
+
try:
|
|
710
|
+
should_retry = False
|
|
711
|
+
ret = self._post_request(
|
|
712
|
+
url,
|
|
713
|
+
headers,
|
|
714
|
+
json.dumps(body, cls=SnowflakeRestfulJsonEncoder),
|
|
715
|
+
token=self.token,
|
|
716
|
+
timeout=5,
|
|
717
|
+
no_retry=True,
|
|
718
|
+
)
|
|
719
|
+
if not ret:
|
|
720
|
+
if retry:
|
|
721
|
+
should_retry = True
|
|
722
|
+
else:
|
|
723
|
+
return
|
|
724
|
+
elif ret.get("success"):
|
|
725
|
+
return
|
|
726
|
+
err = ret.get("message")
|
|
727
|
+
if err is not None and ret.get("data"):
|
|
728
|
+
err += ret["data"].get("errorMessage", "")
|
|
729
|
+
# no exception is raised
|
|
730
|
+
logger.debug("error in deleting session. ignoring...: %s", err)
|
|
731
|
+
except Exception as e:
|
|
732
|
+
logger.debug("error in deleting session. ignoring...: %s", e)
|
|
733
|
+
finally:
|
|
734
|
+
num_retries += 1
|
|
735
|
+
|
|
736
|
+
def _get_request(
|
|
737
|
+
self,
|
|
738
|
+
url: str,
|
|
739
|
+
headers: dict[str, str],
|
|
740
|
+
token: str = None,
|
|
741
|
+
external_session_id: str = None,
|
|
742
|
+
timeout: int | None = None,
|
|
743
|
+
is_fetch_query_status: bool = False,
|
|
744
|
+
) -> dict[str, Any]:
|
|
745
|
+
if "Content-Encoding" in headers:
|
|
746
|
+
del headers["Content-Encoding"]
|
|
747
|
+
if "Content-Length" in headers:
|
|
748
|
+
del headers["Content-Length"]
|
|
749
|
+
|
|
750
|
+
full_url = f"{self.server_url}{url}"
|
|
751
|
+
ret = self.fetch(
|
|
752
|
+
"get",
|
|
753
|
+
full_url,
|
|
754
|
+
headers,
|
|
755
|
+
timeout=timeout,
|
|
756
|
+
token=token,
|
|
757
|
+
external_session_id=external_session_id,
|
|
758
|
+
is_fetch_query_status=is_fetch_query_status,
|
|
759
|
+
)
|
|
760
|
+
if (
|
|
761
|
+
ret.get("code") == SESSION_EXPIRED_GS_CODE
|
|
762
|
+
and self._connection._authenticator != PAT_WITH_EXTERNAL_SESSION
|
|
763
|
+
):
|
|
764
|
+
try:
|
|
765
|
+
ret = self._renew_session()
|
|
766
|
+
except ReauthenticationRequest as ex:
|
|
767
|
+
if self._connection._authenticator != EXTERNAL_BROWSER_AUTHENTICATOR:
|
|
768
|
+
raise ex.cause
|
|
769
|
+
ret = self._connection._reauthenticate()
|
|
770
|
+
logger.debug(
|
|
771
|
+
"ret[code] = {code} after renew_session".format(
|
|
772
|
+
code=(ret.get("code", "N/A"))
|
|
773
|
+
)
|
|
774
|
+
)
|
|
775
|
+
if ret.get("success"):
|
|
776
|
+
return self._get_request(
|
|
777
|
+
url,
|
|
778
|
+
headers,
|
|
779
|
+
token=self.token,
|
|
780
|
+
is_fetch_query_status=is_fetch_query_status,
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
return ret
|
|
784
|
+
|
|
785
|
+
def _post_request(
|
|
786
|
+
self,
|
|
787
|
+
url,
|
|
788
|
+
headers,
|
|
789
|
+
body,
|
|
790
|
+
token=None,
|
|
791
|
+
external_session_id: str | None = None,
|
|
792
|
+
timeout: int | None = None,
|
|
793
|
+
socket_timeout: int | None = None,
|
|
794
|
+
_no_results: bool = False,
|
|
795
|
+
no_retry: bool = False,
|
|
796
|
+
_include_retry_params: bool = False,
|
|
797
|
+
):
|
|
798
|
+
full_url = f"{self.server_url}{url}"
|
|
799
|
+
if self._connection._probe_connection:
|
|
800
|
+
from pprint import pprint
|
|
801
|
+
|
|
802
|
+
ret = probe_connection(full_url)
|
|
803
|
+
pprint(ret)
|
|
804
|
+
|
|
805
|
+
ret = self.fetch(
|
|
806
|
+
"post",
|
|
807
|
+
full_url,
|
|
808
|
+
headers,
|
|
809
|
+
data=body,
|
|
810
|
+
timeout=timeout,
|
|
811
|
+
token=token,
|
|
812
|
+
external_session_id=external_session_id,
|
|
813
|
+
no_retry=no_retry,
|
|
814
|
+
_include_retry_params=_include_retry_params,
|
|
815
|
+
socket_timeout=socket_timeout,
|
|
816
|
+
)
|
|
817
|
+
logger.debug(
|
|
818
|
+
"ret[code] = {code}, after post request".format(
|
|
819
|
+
code=(ret.get("code", "N/A"))
|
|
820
|
+
)
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
if ret.get("code") == MASTER_TOKEN_EXPIRED_GS_CODE:
|
|
824
|
+
self._connection.expired = True
|
|
825
|
+
elif (
|
|
826
|
+
ret.get("code") == SESSION_EXPIRED_GS_CODE
|
|
827
|
+
and self._connection._authenticator != PAT_WITH_EXTERNAL_SESSION
|
|
828
|
+
):
|
|
829
|
+
try:
|
|
830
|
+
ret = self._renew_session()
|
|
831
|
+
except ReauthenticationRequest as ex:
|
|
832
|
+
if self._connection._authenticator != EXTERNAL_BROWSER_AUTHENTICATOR:
|
|
833
|
+
raise ex.cause
|
|
834
|
+
ret = self._connection._reauthenticate()
|
|
835
|
+
logger.debug(
|
|
836
|
+
"ret[code] = {code} after renew_session".format(
|
|
837
|
+
code=(ret.get("code", "N/A"))
|
|
838
|
+
)
|
|
839
|
+
)
|
|
840
|
+
if ret.get("success"):
|
|
841
|
+
return self._post_request(
|
|
842
|
+
url, headers, body, token=self.token, timeout=timeout
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
if isinstance(ret.get("data"), dict) and ret["data"].get("queryId"):
|
|
846
|
+
logger.debug("Query id: {}".format(ret["data"]["queryId"]))
|
|
847
|
+
|
|
848
|
+
if ret.get("code") == QUERY_IN_PROGRESS_ASYNC_CODE and _no_results:
|
|
849
|
+
return ret
|
|
850
|
+
|
|
851
|
+
while ret.get("code") in (QUERY_IN_PROGRESS_CODE, QUERY_IN_PROGRESS_ASYNC_CODE):
|
|
852
|
+
if self._inject_client_pause > 0:
|
|
853
|
+
logger.debug("waiting for %s...", self._inject_client_pause)
|
|
854
|
+
time.sleep(self._inject_client_pause)
|
|
855
|
+
# ping pong
|
|
856
|
+
result_url = ret["data"]["getResultUrl"]
|
|
857
|
+
logger.debug("ping pong starting...")
|
|
858
|
+
ret = self._get_request(
|
|
859
|
+
result_url,
|
|
860
|
+
headers,
|
|
861
|
+
token=self.token,
|
|
862
|
+
timeout=timeout,
|
|
863
|
+
is_fetch_query_status=bool(
|
|
864
|
+
re.match(r"^/queries/.+/result$", result_url)
|
|
865
|
+
),
|
|
866
|
+
)
|
|
867
|
+
logger.debug("ret[code] = %s", ret.get("code", "N/A"))
|
|
868
|
+
logger.debug("ping pong done")
|
|
869
|
+
|
|
870
|
+
return ret
|
|
871
|
+
|
|
872
|
+
def fetch(
|
|
873
|
+
self,
|
|
874
|
+
method: str,
|
|
875
|
+
full_url: str,
|
|
876
|
+
headers: dict[str, Any],
|
|
877
|
+
data: dict[str, Any] | None = None,
|
|
878
|
+
timeout: int | None = None,
|
|
879
|
+
**kwargs,
|
|
880
|
+
) -> dict[Any, Any]:
|
|
881
|
+
"""Carry out API request with session management."""
|
|
882
|
+
|
|
883
|
+
class RetryCtx(TimeoutBackoffCtx):
|
|
884
|
+
def __init__(
|
|
885
|
+
self,
|
|
886
|
+
_include_retry_params: bool = False,
|
|
887
|
+
_include_retry_reason: bool = False,
|
|
888
|
+
**kwargs,
|
|
889
|
+
) -> None:
|
|
890
|
+
super().__init__(**kwargs)
|
|
891
|
+
self.retry_reason = 0
|
|
892
|
+
self._include_retry_params = _include_retry_params
|
|
893
|
+
self._include_retry_reason = _include_retry_reason
|
|
894
|
+
|
|
895
|
+
def add_retry_params(self, full_url: str) -> str:
|
|
896
|
+
if self._include_retry_params and self.current_retry_count > 0:
|
|
897
|
+
retry_params = {
|
|
898
|
+
"clientStartTime": self._start_time_millis,
|
|
899
|
+
"retryCount": self.current_retry_count,
|
|
900
|
+
}
|
|
901
|
+
if self._include_retry_reason:
|
|
902
|
+
retry_params.update({"retryReason": self.retry_reason})
|
|
903
|
+
suffix = urlencode(retry_params)
|
|
904
|
+
sep = "&" if urlparse(full_url).query else "?"
|
|
905
|
+
return full_url + sep + suffix
|
|
906
|
+
else:
|
|
907
|
+
return full_url
|
|
908
|
+
|
|
909
|
+
include_retry_reason = self._connection._enable_retry_reason_in_query_response
|
|
910
|
+
include_retry_params = kwargs.pop("_include_retry_params", False)
|
|
911
|
+
|
|
912
|
+
with self._use_requests_session(full_url) as session:
|
|
913
|
+
retry_ctx = RetryCtx(
|
|
914
|
+
_include_retry_params=include_retry_params,
|
|
915
|
+
_include_retry_reason=include_retry_reason,
|
|
916
|
+
timeout=(
|
|
917
|
+
timeout if timeout is not None else self._connection.network_timeout
|
|
918
|
+
),
|
|
919
|
+
backoff_generator=self._connection._backoff_generator,
|
|
920
|
+
)
|
|
921
|
+
|
|
922
|
+
retry_ctx.set_start_time()
|
|
923
|
+
while True:
|
|
924
|
+
ret = self._request_exec_wrapper(
|
|
925
|
+
session, method, full_url, headers, data, retry_ctx, **kwargs
|
|
926
|
+
)
|
|
927
|
+
if ret is not None:
|
|
928
|
+
return ret
|
|
929
|
+
|
|
930
|
+
@staticmethod
|
|
931
|
+
def add_request_guid(full_url: str) -> str:
|
|
932
|
+
"""Adds request_guid parameter for HTTP request tracing."""
|
|
933
|
+
parsed_url = urlparse(full_url)
|
|
934
|
+
if not re.search(_SNOWFLAKE_HOST_SUFFIX_REGEX, parsed_url.hostname):
|
|
935
|
+
return full_url
|
|
936
|
+
request_guid = str(uuid.uuid4())
|
|
937
|
+
suffix = urlencode({REQUEST_GUID: request_guid})
|
|
938
|
+
logger.debug(f"Request guid: {request_guid}")
|
|
939
|
+
sep = "&" if parsed_url.query else "?"
|
|
940
|
+
# url has query string already, just add fields
|
|
941
|
+
return full_url + sep + suffix
|
|
942
|
+
|
|
943
|
+
def _request_exec_wrapper(
|
|
944
|
+
self,
|
|
945
|
+
session,
|
|
946
|
+
method,
|
|
947
|
+
full_url,
|
|
948
|
+
headers,
|
|
949
|
+
data,
|
|
950
|
+
retry_ctx,
|
|
951
|
+
no_retry: bool = False,
|
|
952
|
+
token=NO_TOKEN,
|
|
953
|
+
external_session_id=NO_EXTERNAL_SESSION_ID,
|
|
954
|
+
**kwargs,
|
|
955
|
+
):
|
|
956
|
+
conn = self._connection
|
|
957
|
+
logger.debug(
|
|
958
|
+
"remaining request timeout: %s ms, retry cnt: %s",
|
|
959
|
+
retry_ctx.remaining_time_millis if retry_ctx.timeout is not None else "N/A",
|
|
960
|
+
retry_ctx.current_retry_count + 1,
|
|
961
|
+
)
|
|
962
|
+
|
|
963
|
+
full_url = retry_ctx.add_retry_params(full_url)
|
|
964
|
+
full_url = SnowflakeRestful.add_request_guid(full_url)
|
|
965
|
+
is_fetch_query_status = kwargs.pop("is_fetch_query_status", False)
|
|
966
|
+
# raise_raw_http_failure is not a public parameter and may change in the future
|
|
967
|
+
# it enables raising raw http errors that are not handled by
|
|
968
|
+
# connector, connector handles the following http error:
|
|
969
|
+
# 1. FORBIDDEN error when trying to login
|
|
970
|
+
# 2. retryable http code defined in method is_retryable_http_code
|
|
971
|
+
# 3. UNAUTHORIZED error when using okta authentication
|
|
972
|
+
# raise_raw_http_failure doesn't work for the 3 mentioned cases.
|
|
973
|
+
raise_raw_http_failure = kwargs.pop("raise_raw_http_failure", False)
|
|
974
|
+
try:
|
|
975
|
+
return_object = self._request_exec(
|
|
976
|
+
session=session,
|
|
977
|
+
method=method,
|
|
978
|
+
full_url=full_url,
|
|
979
|
+
headers=headers,
|
|
980
|
+
data=data,
|
|
981
|
+
token=token,
|
|
982
|
+
external_session_id=external_session_id,
|
|
983
|
+
raise_raw_http_failure=raise_raw_http_failure,
|
|
984
|
+
**kwargs,
|
|
985
|
+
)
|
|
986
|
+
if return_object is not None:
|
|
987
|
+
return return_object
|
|
988
|
+
if is_fetch_query_status:
|
|
989
|
+
err_msg = "fetch query status failed and http request returned None, this is usually caused by transient network failures, retrying..."
|
|
990
|
+
logger.info(err_msg)
|
|
991
|
+
raise RetryRequest(err_msg)
|
|
992
|
+
self._handle_unknown_error(method, full_url, headers, data, conn)
|
|
993
|
+
return {}
|
|
994
|
+
except RetryRequest as e:
|
|
995
|
+
cause = e.args[0]
|
|
996
|
+
if no_retry:
|
|
997
|
+
self.log_and_handle_http_error_with_cause(
|
|
998
|
+
e,
|
|
999
|
+
full_url,
|
|
1000
|
+
method,
|
|
1001
|
+
retry_ctx.timeout,
|
|
1002
|
+
retry_ctx.current_retry_count,
|
|
1003
|
+
conn,
|
|
1004
|
+
timed_out=False,
|
|
1005
|
+
)
|
|
1006
|
+
return {} # required for tests
|
|
1007
|
+
if not retry_ctx.should_retry:
|
|
1008
|
+
self.log_and_handle_http_error_with_cause(
|
|
1009
|
+
e,
|
|
1010
|
+
full_url,
|
|
1011
|
+
method,
|
|
1012
|
+
retry_ctx.timeout,
|
|
1013
|
+
retry_ctx.current_retry_count,
|
|
1014
|
+
conn,
|
|
1015
|
+
)
|
|
1016
|
+
return {} # required for tests
|
|
1017
|
+
|
|
1018
|
+
logger.debug(
|
|
1019
|
+
"retrying: errorclass=%s, "
|
|
1020
|
+
"error=%s, "
|
|
1021
|
+
"counter=%s, "
|
|
1022
|
+
"sleeping=%s(s)",
|
|
1023
|
+
type(cause),
|
|
1024
|
+
cause,
|
|
1025
|
+
retry_ctx.current_retry_count + 1,
|
|
1026
|
+
retry_ctx.current_sleep_time,
|
|
1027
|
+
)
|
|
1028
|
+
time.sleep(float(retry_ctx.current_sleep_time))
|
|
1029
|
+
retry_ctx.increment()
|
|
1030
|
+
|
|
1031
|
+
reason = getattr(cause, "errno", 0)
|
|
1032
|
+
retry_ctx.retry_reason = reason
|
|
1033
|
+
|
|
1034
|
+
if "Connection aborted" in repr(e) and "ECONNRESET" in repr(e):
|
|
1035
|
+
# connection is reset by the server, the underlying connection is broken and can not be reused
|
|
1036
|
+
# we need a new urllib3 http(s) connection in this case.
|
|
1037
|
+
# We need to first close the old one so that urllib3 pool manager can create a new connection
|
|
1038
|
+
# for new requests
|
|
1039
|
+
try:
|
|
1040
|
+
logger.debug(
|
|
1041
|
+
"shutting down requests session adapter due to connection aborted"
|
|
1042
|
+
)
|
|
1043
|
+
session.get_adapter(full_url).close()
|
|
1044
|
+
except Exception as close_adapter_exc:
|
|
1045
|
+
logger.debug(
|
|
1046
|
+
"Ignored error caused by closing https connection failure: %s",
|
|
1047
|
+
close_adapter_exc,
|
|
1048
|
+
)
|
|
1049
|
+
return None # retry
|
|
1050
|
+
except Exception as e:
|
|
1051
|
+
if (
|
|
1052
|
+
raise_raw_http_failure and isinstance(e, requests.exceptions.HTTPError)
|
|
1053
|
+
) or not no_retry:
|
|
1054
|
+
raise e
|
|
1055
|
+
logger.debug("Ignored error", exc_info=True)
|
|
1056
|
+
return {}
|
|
1057
|
+
|
|
1058
|
+
def log_and_handle_http_error_with_cause(
|
|
1059
|
+
self,
|
|
1060
|
+
e: Exception,
|
|
1061
|
+
full_url: str,
|
|
1062
|
+
method: str,
|
|
1063
|
+
retry_timeout: int,
|
|
1064
|
+
retry_count: int,
|
|
1065
|
+
conn: SnowflakeConnection,
|
|
1066
|
+
timed_out: bool = True,
|
|
1067
|
+
) -> None:
|
|
1068
|
+
cause = e.args[0]
|
|
1069
|
+
logger.error(cause, exc_info=True)
|
|
1070
|
+
if isinstance(cause, Error):
|
|
1071
|
+
Error.errorhandler_wrapper_from_cause(conn, cause)
|
|
1072
|
+
else:
|
|
1073
|
+
self.handle_invalid_certificate_error(conn, full_url, cause)
|
|
1074
|
+
|
|
1075
|
+
def handle_invalid_certificate_error(self, conn, full_url, cause) -> None:
|
|
1076
|
+
# all other errors raise exception
|
|
1077
|
+
Error.errorhandler_wrapper(
|
|
1078
|
+
conn,
|
|
1079
|
+
None,
|
|
1080
|
+
OperationalError,
|
|
1081
|
+
{
|
|
1082
|
+
"msg": f"Failed to execute request: {cause}",
|
|
1083
|
+
"errno": ER_FAILED_TO_REQUEST,
|
|
1084
|
+
},
|
|
1085
|
+
)
|
|
1086
|
+
|
|
1087
|
+
def _handle_unknown_error(self, method, full_url, headers, data, conn) -> None:
|
|
1088
|
+
"""Handles unknown errors."""
|
|
1089
|
+
if data:
|
|
1090
|
+
_, masked_data, err_str = SecretDetector.mask_secrets(data)
|
|
1091
|
+
if err_str is None:
|
|
1092
|
+
data = masked_data
|
|
1093
|
+
logger.error(
|
|
1094
|
+
f"Failed to get the response. Hanging? "
|
|
1095
|
+
f"method: {method}, url: {full_url}, headers:{headers}, "
|
|
1096
|
+
f"data: {data}"
|
|
1097
|
+
)
|
|
1098
|
+
Error.errorhandler_wrapper(
|
|
1099
|
+
conn,
|
|
1100
|
+
None,
|
|
1101
|
+
OperationalError,
|
|
1102
|
+
{
|
|
1103
|
+
"msg": f"Failed to get the response. Hanging? method: {method}, url: {full_url}",
|
|
1104
|
+
"errno": ER_FAILED_TO_REQUEST,
|
|
1105
|
+
},
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1108
|
+
def _request_exec(
|
|
1109
|
+
self,
|
|
1110
|
+
session,
|
|
1111
|
+
method,
|
|
1112
|
+
full_url,
|
|
1113
|
+
headers,
|
|
1114
|
+
data,
|
|
1115
|
+
token,
|
|
1116
|
+
external_session_id=None,
|
|
1117
|
+
catch_okta_unauthorized_error: bool = False,
|
|
1118
|
+
is_raw_text: bool = False,
|
|
1119
|
+
is_raw_binary: bool = False,
|
|
1120
|
+
binary_data_handler=None,
|
|
1121
|
+
socket_timeout: int | None = None,
|
|
1122
|
+
is_okta_authentication: bool = False,
|
|
1123
|
+
raise_raw_http_failure: bool = False,
|
|
1124
|
+
):
|
|
1125
|
+
if socket_timeout is None:
|
|
1126
|
+
if self._connection.socket_timeout is not None:
|
|
1127
|
+
logger.debug("socket_timeout specified in connection")
|
|
1128
|
+
socket_timeout = self._connection.socket_timeout
|
|
1129
|
+
else:
|
|
1130
|
+
socket_timeout = DEFAULT_SOCKET_CONNECT_TIMEOUT
|
|
1131
|
+
logger.debug("socket timeout: %s", socket_timeout)
|
|
1132
|
+
|
|
1133
|
+
try:
|
|
1134
|
+
if not catch_okta_unauthorized_error and data and len(data) > 0:
|
|
1135
|
+
headers["Content-Encoding"] = "gzip"
|
|
1136
|
+
input_data = gzip.compress(data.encode("utf-8"))
|
|
1137
|
+
else:
|
|
1138
|
+
input_data = data
|
|
1139
|
+
|
|
1140
|
+
download_start_time = get_time_millis()
|
|
1141
|
+
# socket timeout is constant. You should be able to receive
|
|
1142
|
+
# the response within the time. If not, ConnectReadTimeout or
|
|
1143
|
+
# ReadTimeout is raised.
|
|
1144
|
+
auth = (
|
|
1145
|
+
PATWithExternalSessionAuth(token, external_session_id)
|
|
1146
|
+
if (external_session_id is not None and token is not None)
|
|
1147
|
+
else SnowflakeAuth(token)
|
|
1148
|
+
)
|
|
1149
|
+
raw_ret = session.request(
|
|
1150
|
+
method=method,
|
|
1151
|
+
url=full_url,
|
|
1152
|
+
headers=headers,
|
|
1153
|
+
data=input_data,
|
|
1154
|
+
timeout=socket_timeout,
|
|
1155
|
+
verify=True,
|
|
1156
|
+
stream=is_raw_binary,
|
|
1157
|
+
auth=auth,
|
|
1158
|
+
)
|
|
1159
|
+
download_end_time = get_time_millis()
|
|
1160
|
+
|
|
1161
|
+
try:
|
|
1162
|
+
if raw_ret.status_code == OK:
|
|
1163
|
+
logger.debug("SUCCESS")
|
|
1164
|
+
if is_raw_text:
|
|
1165
|
+
ret = raw_ret.text
|
|
1166
|
+
elif is_raw_binary:
|
|
1167
|
+
ret = binary_data_handler.to_iterator(
|
|
1168
|
+
raw_ret.raw, download_end_time - download_start_time
|
|
1169
|
+
)
|
|
1170
|
+
else:
|
|
1171
|
+
ret = raw_ret.json()
|
|
1172
|
+
return ret
|
|
1173
|
+
|
|
1174
|
+
if is_login_request(full_url) and raw_ret.status_code == FORBIDDEN:
|
|
1175
|
+
raise ForbiddenError
|
|
1176
|
+
|
|
1177
|
+
elif is_retryable_http_code(raw_ret.status_code):
|
|
1178
|
+
err = get_http_retryable_error(raw_ret.status_code)
|
|
1179
|
+
# retryable server exceptions
|
|
1180
|
+
if is_okta_authentication:
|
|
1181
|
+
raise RefreshTokenError(
|
|
1182
|
+
msg="OKTA authentication requires token refresh."
|
|
1183
|
+
)
|
|
1184
|
+
if is_login_request(full_url):
|
|
1185
|
+
logger.debug(
|
|
1186
|
+
"Received retryable response code while logging in. Will be handled by "
|
|
1187
|
+
f"authenticator. Ignore the following. Error stack: {err}",
|
|
1188
|
+
exc_info=True,
|
|
1189
|
+
)
|
|
1190
|
+
raise OperationalError(
|
|
1191
|
+
msg="Login request is retryable. Will be handled by authenticator",
|
|
1192
|
+
errno=ER_RETRYABLE_CODE,
|
|
1193
|
+
)
|
|
1194
|
+
else:
|
|
1195
|
+
logger.debug(f"{err}. Retrying...")
|
|
1196
|
+
raise RetryRequest(err)
|
|
1197
|
+
|
|
1198
|
+
elif (
|
|
1199
|
+
raw_ret.status_code == UNAUTHORIZED
|
|
1200
|
+
and catch_okta_unauthorized_error
|
|
1201
|
+
):
|
|
1202
|
+
# OKTA Unauthorized errors
|
|
1203
|
+
raise_okta_unauthorized_error(self._connection, raw_ret)
|
|
1204
|
+
return None # required for tests
|
|
1205
|
+
else:
|
|
1206
|
+
if raise_raw_http_failure:
|
|
1207
|
+
raw_ret.raise_for_status()
|
|
1208
|
+
raise_failed_request_error(
|
|
1209
|
+
self._connection, full_url, method, raw_ret
|
|
1210
|
+
)
|
|
1211
|
+
return None # required for tests
|
|
1212
|
+
finally:
|
|
1213
|
+
raw_ret.close() # ensure response is closed
|
|
1214
|
+
except SSLError as se:
|
|
1215
|
+
msg = f"Hit non-retryable SSL error, {str(se)}.\n{_CONNECTIVITY_ERR_MSG}"
|
|
1216
|
+
logger.debug(msg)
|
|
1217
|
+
# the following code is for backward compatibility with old versions of python connector which calls
|
|
1218
|
+
# self._handle_unknown_error to process SSLError
|
|
1219
|
+
Error.errorhandler_wrapper(
|
|
1220
|
+
self._connection,
|
|
1221
|
+
None,
|
|
1222
|
+
OperationalError,
|
|
1223
|
+
{
|
|
1224
|
+
"msg": msg,
|
|
1225
|
+
"errno": ER_FAILED_TO_REQUEST,
|
|
1226
|
+
},
|
|
1227
|
+
)
|
|
1228
|
+
except (
|
|
1229
|
+
BadStatusLine,
|
|
1230
|
+
ConnectionError,
|
|
1231
|
+
ConnectTimeout,
|
|
1232
|
+
IncompleteRead,
|
|
1233
|
+
ProtocolError, # from urllib3 # from urllib3
|
|
1234
|
+
OpenSSL.SSL.SysCallError,
|
|
1235
|
+
KeyError, # SNOW-39175: asn1crypto.keys.PublicKeyInfo
|
|
1236
|
+
ValueError,
|
|
1237
|
+
ReadTimeout,
|
|
1238
|
+
RuntimeError,
|
|
1239
|
+
AttributeError, # json decoding error
|
|
1240
|
+
) as err:
|
|
1241
|
+
if is_login_request(full_url):
|
|
1242
|
+
logger.debug(
|
|
1243
|
+
"Hit a timeout error while logging in. Will be handled by "
|
|
1244
|
+
f"authenticator. Ignore the following. Error stack: {err}",
|
|
1245
|
+
exc_info=True,
|
|
1246
|
+
)
|
|
1247
|
+
raise OperationalError(
|
|
1248
|
+
msg="ConnectionTimeout occurred during login. Will be handled by authenticator",
|
|
1249
|
+
errno=ER_CONNECTION_TIMEOUT,
|
|
1250
|
+
)
|
|
1251
|
+
else:
|
|
1252
|
+
logger.debug(
|
|
1253
|
+
"Hit retryable client error. Retrying... Ignore the following "
|
|
1254
|
+
f"error stack: {err}",
|
|
1255
|
+
exc_info=True,
|
|
1256
|
+
)
|
|
1257
|
+
raise RetryRequest(err)
|
|
1258
|
+
except Exception as err:
|
|
1259
|
+
raise err
|
|
1260
|
+
|
|
1261
|
+
def make_requests_session(self) -> Session:
|
|
1262
|
+
s = requests.Session()
|
|
1263
|
+
s.mount("http://", ProxySupportAdapter(max_retries=REQUESTS_RETRY))
|
|
1264
|
+
s.mount("https://", ProxySupportAdapter(max_retries=REQUESTS_RETRY))
|
|
1265
|
+
s._reuse_count = itertools.count()
|
|
1266
|
+
return s
|
|
1267
|
+
|
|
1268
|
+
@contextlib.contextmanager
|
|
1269
|
+
def _use_requests_session(self, url: str | None = None):
|
|
1270
|
+
"""Session caching context manager.
|
|
1271
|
+
|
|
1272
|
+
Notes:
|
|
1273
|
+
The session is not closed until close() is called so each session may be used multiple times.
|
|
1274
|
+
"""
|
|
1275
|
+
# short-lived session, not added to the _sessions_map
|
|
1276
|
+
if self._connection.disable_request_pooling:
|
|
1277
|
+
session = self.make_requests_session()
|
|
1278
|
+
try:
|
|
1279
|
+
yield session
|
|
1280
|
+
finally:
|
|
1281
|
+
session.close()
|
|
1282
|
+
else:
|
|
1283
|
+
try:
|
|
1284
|
+
hostname = urlparse(url).hostname
|
|
1285
|
+
except Exception:
|
|
1286
|
+
hostname = None
|
|
1287
|
+
|
|
1288
|
+
session_pool: SessionPool = self._sessions_map[hostname]
|
|
1289
|
+
session = session_pool.get_session()
|
|
1290
|
+
logger.debug(f"Session status for SessionPool '{hostname}', {session_pool}")
|
|
1291
|
+
try:
|
|
1292
|
+
yield session
|
|
1293
|
+
finally:
|
|
1294
|
+
session_pool.return_session(session)
|
|
1295
|
+
logger.debug(
|
|
1296
|
+
f"Session status for SessionPool '{hostname}', {session_pool}"
|
|
1297
|
+
)
|