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,276 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import xml.etree.ElementTree as ET
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from logging import getLogger
|
|
9
|
+
from random import choice
|
|
10
|
+
from string import hexdigits
|
|
11
|
+
from typing import TYPE_CHECKING, Any, NamedTuple
|
|
12
|
+
|
|
13
|
+
from .compat import quote
|
|
14
|
+
from .constants import FileHeader, ResultStatus
|
|
15
|
+
from .encryption_util import EncryptionMetadata
|
|
16
|
+
from .storage_client import SnowflakeStorageClient
|
|
17
|
+
from .util_text import get_md5
|
|
18
|
+
from .vendored import requests
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
21
|
+
from .file_transfer_agent import SnowflakeFileMeta, StorageCredential
|
|
22
|
+
|
|
23
|
+
logger = getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AzureLocation(NamedTuple):
|
|
27
|
+
container_name: str
|
|
28
|
+
path: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
TOKEN_EXPIRATION_ERR_MESSAGE = (
|
|
32
|
+
"Signature not valid in the specified time frame",
|
|
33
|
+
"Server failed to authenticate the request.",
|
|
34
|
+
)
|
|
35
|
+
SFCDIGEST = "x-ms-meta-sfcdigest"
|
|
36
|
+
ENCRYPTION_DATA = "x-ms-meta-encryptiondata"
|
|
37
|
+
MATDESC = "x-ms-meta-matdesc"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SnowflakeAzureRestClient(SnowflakeStorageClient):
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
meta: SnowflakeFileMeta,
|
|
44
|
+
credentials: StorageCredential | None,
|
|
45
|
+
chunk_size: int,
|
|
46
|
+
stage_info: dict[str, Any],
|
|
47
|
+
unsafe_file_write: bool = False,
|
|
48
|
+
) -> None:
|
|
49
|
+
super().__init__(
|
|
50
|
+
meta,
|
|
51
|
+
stage_info,
|
|
52
|
+
chunk_size,
|
|
53
|
+
credentials=credentials,
|
|
54
|
+
unsafe_file_write=unsafe_file_write,
|
|
55
|
+
)
|
|
56
|
+
end_point: str = stage_info["endPoint"]
|
|
57
|
+
if end_point.startswith("blob."):
|
|
58
|
+
end_point = end_point[len("blob.") :]
|
|
59
|
+
self.endpoint = end_point
|
|
60
|
+
self.storage_account: str = stage_info["storageAccount"]
|
|
61
|
+
self.azure_location = self.extract_container_name_and_path(
|
|
62
|
+
stage_info["location"]
|
|
63
|
+
)
|
|
64
|
+
self.block_ids: list[str] = []
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def extract_container_name_and_path(stage_location: str) -> AzureLocation:
|
|
68
|
+
stage_location = os.path.expanduser(stage_location)
|
|
69
|
+
container_name = stage_location
|
|
70
|
+
path = ""
|
|
71
|
+
|
|
72
|
+
# split stage location as bucket name and path
|
|
73
|
+
if "/" in stage_location:
|
|
74
|
+
container_name, _, path = stage_location.partition("/")
|
|
75
|
+
if path and not path.endswith("/"):
|
|
76
|
+
path += "/"
|
|
77
|
+
|
|
78
|
+
return AzureLocation(container_name=container_name, path=path)
|
|
79
|
+
|
|
80
|
+
def _has_expired_token(self, response: requests.Response) -> bool:
|
|
81
|
+
return response.status_code == 403 and any(
|
|
82
|
+
message in response.reason for message in TOKEN_EXPIRATION_ERR_MESSAGE
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def _send_request_with_authentication_and_retry(
|
|
86
|
+
self,
|
|
87
|
+
verb: str,
|
|
88
|
+
url: str,
|
|
89
|
+
retry_id: int | str,
|
|
90
|
+
headers: dict[str, Any] = None,
|
|
91
|
+
data: bytes = None,
|
|
92
|
+
) -> requests.Response:
|
|
93
|
+
if not headers:
|
|
94
|
+
headers = {}
|
|
95
|
+
|
|
96
|
+
def generate_authenticated_url_and_rest_args() -> tuple[bytes, dict[str, Any]]:
|
|
97
|
+
curtime = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
98
|
+
timestamp = curtime.strftime("YYYY-MM-DD")
|
|
99
|
+
sas_token = self.credentials.creds["AZURE_SAS_TOKEN"]
|
|
100
|
+
if sas_token and sas_token.startswith("?"):
|
|
101
|
+
sas_token = sas_token[1:]
|
|
102
|
+
if "?" in url:
|
|
103
|
+
_url = url + "&" + sas_token
|
|
104
|
+
else:
|
|
105
|
+
_url = url + "?" + sas_token
|
|
106
|
+
headers["Date"] = timestamp
|
|
107
|
+
rest_args = {"headers": headers}
|
|
108
|
+
if data:
|
|
109
|
+
rest_args["data"] = data
|
|
110
|
+
return _url, rest_args
|
|
111
|
+
|
|
112
|
+
return self._send_request_with_retry(
|
|
113
|
+
verb, generate_authenticated_url_and_rest_args, retry_id
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def get_file_header(self, filename: str) -> FileHeader | None:
|
|
117
|
+
"""Gets Azure file properties."""
|
|
118
|
+
container_name = quote(self.azure_location.container_name)
|
|
119
|
+
path = quote(self.azure_location.path) + quote(filename)
|
|
120
|
+
meta = self.meta
|
|
121
|
+
# HTTP HEAD request
|
|
122
|
+
url = f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}"
|
|
123
|
+
retry_id = "HEAD"
|
|
124
|
+
self.retry_count[retry_id] = 0
|
|
125
|
+
r = self._send_request_with_authentication_and_retry("HEAD", url, retry_id)
|
|
126
|
+
if r.status_code == 200:
|
|
127
|
+
meta.result_status = ResultStatus.UPLOADED
|
|
128
|
+
enc_data_str = r.headers.get(ENCRYPTION_DATA)
|
|
129
|
+
encryption_data = None if enc_data_str is None else json.loads(enc_data_str)
|
|
130
|
+
encryption_metadata = (
|
|
131
|
+
None
|
|
132
|
+
if not encryption_data
|
|
133
|
+
else EncryptionMetadata(
|
|
134
|
+
key=encryption_data["WrappedContentKey"]["EncryptedKey"],
|
|
135
|
+
iv=encryption_data["ContentEncryptionIV"],
|
|
136
|
+
matdesc=r.headers.get(MATDESC),
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
return FileHeader(
|
|
140
|
+
digest=r.headers.get(SFCDIGEST),
|
|
141
|
+
content_length=int(r.headers.get("Content-Length")),
|
|
142
|
+
encryption_metadata=encryption_metadata,
|
|
143
|
+
)
|
|
144
|
+
elif r.status_code == 404:
|
|
145
|
+
meta.result_status = ResultStatus.NOT_FOUND_FILE
|
|
146
|
+
return FileHeader(
|
|
147
|
+
digest=None, content_length=None, encryption_metadata=None
|
|
148
|
+
)
|
|
149
|
+
else:
|
|
150
|
+
r.raise_for_status()
|
|
151
|
+
|
|
152
|
+
def _prepare_file_metadata(self) -> dict[str, str | None]:
|
|
153
|
+
azure_metadata = {
|
|
154
|
+
SFCDIGEST: self.meta.sha256_digest,
|
|
155
|
+
}
|
|
156
|
+
encryption_metadata = self.encryption_metadata
|
|
157
|
+
if encryption_metadata:
|
|
158
|
+
azure_metadata.update(
|
|
159
|
+
{
|
|
160
|
+
ENCRYPTION_DATA: json.dumps(
|
|
161
|
+
{
|
|
162
|
+
"EncryptionMode": "FullBlob",
|
|
163
|
+
"WrappedContentKey": {
|
|
164
|
+
"KeyId": "symmKey1",
|
|
165
|
+
"EncryptedKey": encryption_metadata.key,
|
|
166
|
+
"Algorithm": "AES_CBC_256",
|
|
167
|
+
},
|
|
168
|
+
"EncryptionAgent": {
|
|
169
|
+
"Protocol": "1.0",
|
|
170
|
+
"EncryptionAlgorithm": "AES_CBC_128",
|
|
171
|
+
},
|
|
172
|
+
"ContentEncryptionIV": encryption_metadata.iv,
|
|
173
|
+
"KeyWrappingMetadata": {"EncryptionLibrary": "Java 5.3.0"},
|
|
174
|
+
}
|
|
175
|
+
),
|
|
176
|
+
MATDESC: encryption_metadata.matdesc,
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
return azure_metadata
|
|
180
|
+
|
|
181
|
+
def _initiate_multipart_upload(self) -> None:
|
|
182
|
+
self.block_ids = [
|
|
183
|
+
"".join(choice(hexdigits) for _ in range(20))
|
|
184
|
+
for _ in range(self.num_of_chunks)
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
|
|
188
|
+
container_name = quote(self.azure_location.container_name)
|
|
189
|
+
path = quote(self.azure_location.path + self.meta.dst_file_name.lstrip("/"))
|
|
190
|
+
|
|
191
|
+
if self.num_of_chunks > 1:
|
|
192
|
+
block_id = self.block_ids[chunk_id]
|
|
193
|
+
url = (
|
|
194
|
+
f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}?comp=block"
|
|
195
|
+
f"&blockid={block_id}"
|
|
196
|
+
)
|
|
197
|
+
headers = {"Content-Length": str(len(chunk))}
|
|
198
|
+
r = self._send_request_with_authentication_and_retry(
|
|
199
|
+
"PUT", url, chunk_id, headers=headers, data=chunk
|
|
200
|
+
)
|
|
201
|
+
else:
|
|
202
|
+
# single request
|
|
203
|
+
azure_metadata = self._prepare_file_metadata()
|
|
204
|
+
url = f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}"
|
|
205
|
+
headers = {
|
|
206
|
+
"x-ms-blob-type": "BlockBlob",
|
|
207
|
+
"Content-Encoding": "utf-8",
|
|
208
|
+
}
|
|
209
|
+
headers.update(azure_metadata)
|
|
210
|
+
r = self._send_request_with_authentication_and_retry(
|
|
211
|
+
"PUT", url, chunk_id, headers=headers, data=chunk
|
|
212
|
+
)
|
|
213
|
+
r.raise_for_status() # expect status code 201
|
|
214
|
+
|
|
215
|
+
def _complete_multipart_upload(self) -> None:
|
|
216
|
+
container_name = quote(self.azure_location.container_name)
|
|
217
|
+
path = quote(self.azure_location.path + self.meta.dst_file_name.lstrip("/"))
|
|
218
|
+
url = (
|
|
219
|
+
f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}?comp"
|
|
220
|
+
f"=blocklist"
|
|
221
|
+
)
|
|
222
|
+
root = ET.Element("BlockList")
|
|
223
|
+
for block_id in self.block_ids:
|
|
224
|
+
part = ET.Element("Latest")
|
|
225
|
+
part.text = block_id
|
|
226
|
+
root.append(part)
|
|
227
|
+
# SNOW-1778088: We need to calculate the MD5 sum of this file for Azure Blob storage
|
|
228
|
+
new_stream = not bool(self.meta.src_stream or self.meta.intermediate_stream)
|
|
229
|
+
fd = (
|
|
230
|
+
self.meta.src_stream
|
|
231
|
+
or self.meta.intermediate_stream
|
|
232
|
+
or open(self.meta.real_src_file_name, "rb")
|
|
233
|
+
)
|
|
234
|
+
try:
|
|
235
|
+
if not new_stream:
|
|
236
|
+
# Reset position in file
|
|
237
|
+
fd.seek(0)
|
|
238
|
+
file_content = fd.read()
|
|
239
|
+
finally:
|
|
240
|
+
if new_stream:
|
|
241
|
+
fd.close()
|
|
242
|
+
headers = {
|
|
243
|
+
"x-ms-blob-content-encoding": "utf-8",
|
|
244
|
+
"x-ms-blob-content-md5": base64.b64encode(get_md5(file_content)).decode(
|
|
245
|
+
"utf-8"
|
|
246
|
+
),
|
|
247
|
+
}
|
|
248
|
+
azure_metadata = self._prepare_file_metadata()
|
|
249
|
+
headers.update(azure_metadata)
|
|
250
|
+
retry_id = "COMPLETE"
|
|
251
|
+
self.retry_count[retry_id] = 0
|
|
252
|
+
r = self._send_request_with_authentication_and_retry(
|
|
253
|
+
"PUT", url, "COMPLETE", headers=headers, data=ET.tostring(root)
|
|
254
|
+
)
|
|
255
|
+
r.raise_for_status() # expects status code 201
|
|
256
|
+
|
|
257
|
+
def download_chunk(self, chunk_id: int) -> None:
|
|
258
|
+
container_name = quote(self.azure_location.container_name)
|
|
259
|
+
path = quote(self.azure_location.path + self.meta.src_file_name.lstrip("/"))
|
|
260
|
+
url = f"https://{self.storage_account}.blob.{self.endpoint}/{container_name}/{path}"
|
|
261
|
+
if self.num_of_chunks > 1:
|
|
262
|
+
chunk_size = self.chunk_size
|
|
263
|
+
if chunk_id < self.num_of_chunks - 1:
|
|
264
|
+
_range = f"{chunk_id * chunk_size}-{(chunk_id+1)*chunk_size-1}"
|
|
265
|
+
else:
|
|
266
|
+
_range = f"{chunk_id * chunk_size}-"
|
|
267
|
+
headers = {"Range": f"bytes={_range}"}
|
|
268
|
+
r = self._send_request_with_authentication_and_retry(
|
|
269
|
+
"GET", url, chunk_id, headers=headers
|
|
270
|
+
) # expect 206
|
|
271
|
+
else:
|
|
272
|
+
# single request
|
|
273
|
+
r = self._send_request_with_authentication_and_retry("GET", url, chunk_id)
|
|
274
|
+
if r.status_code in (200, 206):
|
|
275
|
+
self.write_downloaded_chunk(chunk_id, r.content)
|
|
276
|
+
r.raise_for_status()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
from typing import Callable, Iterator
|
|
5
|
+
|
|
6
|
+
"""This module provides common implementations of backoff policies
|
|
7
|
+
|
|
8
|
+
All backoff policies must be implemented as generator functions with the behaviour specified below. These generator
|
|
9
|
+
functions will be called to create iterators yielding backoff durations.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
None
|
|
13
|
+
|
|
14
|
+
Yields:
|
|
15
|
+
int: Next backoff duration in seconds
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
This is an example of a valid backoff policy that always yields a backoff duration of 42 seconds.
|
|
19
|
+
|
|
20
|
+
def constant_backoff() -> int:
|
|
21
|
+
while True
|
|
22
|
+
yield 42
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
Note:
|
|
26
|
+
The functions provided in this module are not backoff policies. They are functions returning backoff policies.
|
|
27
|
+
This is to enable customization of the constants used in backoff computations.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
DEFAULT_BACKOFF_FACTOR = 2
|
|
31
|
+
DEFAULT_BACKOFF_BASE = 1
|
|
32
|
+
DEFAULT_BACKOFF_CAP = 16
|
|
33
|
+
DEFAULT_ENABLE_JITTER = True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def mixed_backoff(
|
|
37
|
+
factor: int = DEFAULT_BACKOFF_FACTOR,
|
|
38
|
+
base: int = DEFAULT_BACKOFF_BASE,
|
|
39
|
+
cap: int = DEFAULT_BACKOFF_CAP,
|
|
40
|
+
enable_jitter: bool = DEFAULT_ENABLE_JITTER,
|
|
41
|
+
) -> Callable[..., Iterator[int]]:
|
|
42
|
+
"""Randomly chooses between exponential and constant backoff. Uses equal jitter.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
factor (int): Exponential base for the exponential term.
|
|
46
|
+
base (int): Initial backoff time in seconds. Constant coefficient for the exponential term.
|
|
47
|
+
cap (int): Maximum backoff time in seconds.
|
|
48
|
+
enable_jitter (int): Whether to enable equal jitter on computed durations. For details see
|
|
49
|
+
https://www.awsarchitectureblog.com/2015/03/backoff.html
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Callable: generator function implementing the mixed backoff policy
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def generator():
|
|
56
|
+
cnt = 0
|
|
57
|
+
sleep = base
|
|
58
|
+
|
|
59
|
+
yield sleep
|
|
60
|
+
while True:
|
|
61
|
+
cnt += 1
|
|
62
|
+
|
|
63
|
+
# equal jitter
|
|
64
|
+
mult_factor = random.choice([-1, 1])
|
|
65
|
+
jitter_amount = 0.5 * sleep * mult_factor if enable_jitter else 0
|
|
66
|
+
sleep = int(
|
|
67
|
+
random.choice(
|
|
68
|
+
[sleep + jitter_amount, base * factor**cnt + jitter_amount]
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
sleep = min(cap, sleep)
|
|
72
|
+
|
|
73
|
+
yield sleep
|
|
74
|
+
|
|
75
|
+
return generator
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def linear_backoff(
|
|
79
|
+
factor: int = DEFAULT_BACKOFF_FACTOR,
|
|
80
|
+
base: int = DEFAULT_BACKOFF_BASE,
|
|
81
|
+
cap: int = DEFAULT_BACKOFF_CAP,
|
|
82
|
+
enable_jitter: bool = DEFAULT_ENABLE_JITTER,
|
|
83
|
+
) -> Callable[..., Iterator[int]]:
|
|
84
|
+
"""Standard linear backoff. Uses full jitter.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
factor (int): Linear increment every iteration.
|
|
88
|
+
base (int): Initial backoff time in seconds.
|
|
89
|
+
cap (int): Maximum backoff time in seconds.
|
|
90
|
+
enable_jitter (int): Whether to enable full jitter on computed durations. For details see
|
|
91
|
+
https://www.awsarchitectureblog.com/2015/03/backoff.html
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Callable: generator function implementing the linear backoff policy
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def generator():
|
|
98
|
+
sleep = base
|
|
99
|
+
|
|
100
|
+
yield sleep
|
|
101
|
+
while True:
|
|
102
|
+
sleep += factor
|
|
103
|
+
sleep = min(cap, sleep)
|
|
104
|
+
|
|
105
|
+
# full jitter
|
|
106
|
+
yield random.randint(0, sleep) if enable_jitter else sleep
|
|
107
|
+
|
|
108
|
+
return generator
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def exponential_backoff(
|
|
112
|
+
factor: int = DEFAULT_BACKOFF_FACTOR,
|
|
113
|
+
base: int = DEFAULT_BACKOFF_BASE,
|
|
114
|
+
cap: int = DEFAULT_BACKOFF_CAP,
|
|
115
|
+
enable_jitter: bool = DEFAULT_ENABLE_JITTER,
|
|
116
|
+
) -> Callable[..., Iterator[int]]:
|
|
117
|
+
"""Standard exponential backoff. Uses full jitter.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
factor (int): Exponential base for the exponential term.
|
|
121
|
+
base (int): Initial backoff time in seconds. Constant coefficient for the exponential term.
|
|
122
|
+
cap (int): Maximum backoff time in seconds.
|
|
123
|
+
enable_jitter (int): Whether to enable full jitter on computed durations. For details see
|
|
124
|
+
https://www.awsarchitectureblog.com/2015/03/backoff.html
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Callable: generator function implementing the exponential backoff policy
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def generator():
|
|
131
|
+
sleep = base
|
|
132
|
+
|
|
133
|
+
yield sleep
|
|
134
|
+
while True:
|
|
135
|
+
sleep *= factor
|
|
136
|
+
sleep = min(cap, sleep)
|
|
137
|
+
|
|
138
|
+
# full jitter
|
|
139
|
+
yield random.randint(0, sleep) if enable_jitter else sleep
|
|
140
|
+
|
|
141
|
+
return generator
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import uuid
|
|
6
|
+
from io import BytesIO
|
|
7
|
+
from logging import getLogger
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from ._utils import (
|
|
11
|
+
_PYTHON_SNOWPARK_USE_SCOPED_TEMP_OBJECTS_STRING,
|
|
12
|
+
get_temp_type_for_object,
|
|
13
|
+
)
|
|
14
|
+
from .errors import BindUploadError, Error
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
17
|
+
from .cursor import SnowflakeCursor
|
|
18
|
+
|
|
19
|
+
logger = getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BindUploadAgent:
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
cursor: SnowflakeCursor,
|
|
27
|
+
rows: list[bytes],
|
|
28
|
+
stream_buffer_size: int = 1024 * 1024 * 10,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Construct an agent that uploads binding parameters as CSV files to a temporary stage.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
cursor: The cursor object.
|
|
34
|
+
rows: Rows of binding parameters in CSV format.
|
|
35
|
+
stream_buffer_size: Size of each file, default to 10MB.
|
|
36
|
+
"""
|
|
37
|
+
self._use_scoped_temp_object = (
|
|
38
|
+
cursor.connection._session_parameters.get(
|
|
39
|
+
_PYTHON_SNOWPARK_USE_SCOPED_TEMP_OBJECTS_STRING, False
|
|
40
|
+
)
|
|
41
|
+
if cursor.connection._session_parameters
|
|
42
|
+
else False
|
|
43
|
+
)
|
|
44
|
+
self._STAGE_NAME = (
|
|
45
|
+
"SNOWPARK_TEMP_STAGE_BIND" if self._use_scoped_temp_object else "SYSTEMBIND"
|
|
46
|
+
)
|
|
47
|
+
self.cursor = cursor
|
|
48
|
+
self.rows = rows
|
|
49
|
+
self._stream_buffer_size = stream_buffer_size
|
|
50
|
+
self.stage_path = f"@{self._STAGE_NAME}/{uuid.uuid4().hex}"
|
|
51
|
+
|
|
52
|
+
def _create_stage(self) -> None:
|
|
53
|
+
create_stage_sql = (
|
|
54
|
+
f"create or replace {get_temp_type_for_object(self._use_scoped_temp_object)} stage {self._STAGE_NAME} "
|
|
55
|
+
"file_format=(type=csv field_optionally_enclosed_by='\"')"
|
|
56
|
+
)
|
|
57
|
+
self.cursor.execute(create_stage_sql)
|
|
58
|
+
|
|
59
|
+
def upload(self) -> None:
|
|
60
|
+
try:
|
|
61
|
+
self._create_stage()
|
|
62
|
+
except Error as err:
|
|
63
|
+
self.cursor.connection._session_parameters[
|
|
64
|
+
"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD"
|
|
65
|
+
] = 0
|
|
66
|
+
logger.debug("Failed to create stage for binding.")
|
|
67
|
+
raise BindUploadError from err
|
|
68
|
+
|
|
69
|
+
row_idx = 0
|
|
70
|
+
while row_idx < len(self.rows):
|
|
71
|
+
f = BytesIO()
|
|
72
|
+
size = 0
|
|
73
|
+
while True:
|
|
74
|
+
f.write(self.rows[row_idx])
|
|
75
|
+
size += len(self.rows[row_idx])
|
|
76
|
+
row_idx += 1
|
|
77
|
+
if row_idx >= len(self.rows) or size >= self._stream_buffer_size:
|
|
78
|
+
break
|
|
79
|
+
try:
|
|
80
|
+
f.seek(0)
|
|
81
|
+
self.cursor._upload_stream(
|
|
82
|
+
input_stream=f,
|
|
83
|
+
stage_location=os.path.join(self.stage_path, f"{row_idx}.csv"),
|
|
84
|
+
options={"source_compression": "auto_detect"},
|
|
85
|
+
)
|
|
86
|
+
except Error as err:
|
|
87
|
+
logger.debug("Failed to upload the bindings file to stage.")
|
|
88
|
+
raise BindUploadError from err
|
|
89
|
+
f.close()
|