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,478 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import tempfile
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
from io import BytesIO
|
|
11
|
+
from logging import getLogger
|
|
12
|
+
from math import ceil
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Callable, NamedTuple
|
|
15
|
+
|
|
16
|
+
import OpenSSL
|
|
17
|
+
|
|
18
|
+
from .constants import (
|
|
19
|
+
HTTP_HEADER_CONTENT_ENCODING,
|
|
20
|
+
REQUEST_CONNECTION_TIMEOUT,
|
|
21
|
+
REQUEST_READ_TIMEOUT,
|
|
22
|
+
FileHeader,
|
|
23
|
+
ResultStatus,
|
|
24
|
+
)
|
|
25
|
+
from .encryption_util import EncryptionMetadata, SnowflakeEncryptionUtil
|
|
26
|
+
from .errors import RequestExceedMaxRetryError
|
|
27
|
+
from .file_util import SnowflakeFileUtil
|
|
28
|
+
from .vendored import requests
|
|
29
|
+
from .vendored.requests import ConnectionError, Timeout
|
|
30
|
+
from .vendored.urllib3 import HTTPResponse
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
33
|
+
from .file_transfer_agent import SnowflakeFileMeta, StorageCredential
|
|
34
|
+
|
|
35
|
+
logger = getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SnowflakeFileEncryptionMaterial(NamedTuple):
|
|
39
|
+
query_stage_master_key: str # query stage master key
|
|
40
|
+
query_id: str # query id
|
|
41
|
+
smk_id: int # SMK id
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
METHODS = {
|
|
45
|
+
"GET": requests.get,
|
|
46
|
+
"PUT": requests.put,
|
|
47
|
+
"POST": requests.post,
|
|
48
|
+
"HEAD": requests.head,
|
|
49
|
+
"DELETE": requests.delete,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def remove_content_encoding(resp: requests.Response, **kwargs) -> None:
|
|
54
|
+
"""Remove content-encoding header and decoder so decompression is not triggered"""
|
|
55
|
+
if HTTP_HEADER_CONTENT_ENCODING in resp.headers:
|
|
56
|
+
if isinstance(resp.raw, HTTPResponse):
|
|
57
|
+
resp.raw._decoder = None
|
|
58
|
+
resp.raw.headers.pop(HTTP_HEADER_CONTENT_ENCODING)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SnowflakeStorageClient(ABC):
|
|
62
|
+
TRANSIENT_HTTP_ERR = (408, 429, 500, 502, 503, 504)
|
|
63
|
+
|
|
64
|
+
TRANSIENT_ERRORS = (OpenSSL.SSL.SysCallError, Timeout, ConnectionError)
|
|
65
|
+
SLEEP_MAX = 16.0
|
|
66
|
+
SLEEP_UNIT = 1.0
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
meta: SnowflakeFileMeta,
|
|
71
|
+
stage_info: dict[str, Any],
|
|
72
|
+
chunk_size: int,
|
|
73
|
+
chunked_transfer: bool | None = True,
|
|
74
|
+
credentials: StorageCredential | None = None,
|
|
75
|
+
max_retry: int = 5,
|
|
76
|
+
unsafe_file_write: bool = False,
|
|
77
|
+
) -> None:
|
|
78
|
+
self.meta = meta
|
|
79
|
+
self.stage_info = stage_info
|
|
80
|
+
self.retry_count: dict[int | str, int] = defaultdict(int)
|
|
81
|
+
self.tmp_dir = tempfile.mkdtemp()
|
|
82
|
+
self.data_file: str | None = None
|
|
83
|
+
self.encryption_metadata: EncryptionMetadata | None = None
|
|
84
|
+
|
|
85
|
+
self.max_retry = max_retry # TODO
|
|
86
|
+
self.credentials = credentials
|
|
87
|
+
# UPLOAD
|
|
88
|
+
meta.real_src_file_name = meta.src_file_name
|
|
89
|
+
meta.upload_size = meta.src_file_size
|
|
90
|
+
self.preprocessed = (
|
|
91
|
+
False # so we don't repeat compression/file digest when re-encrypting
|
|
92
|
+
)
|
|
93
|
+
# DOWNLOAD
|
|
94
|
+
self.full_dst_file_name: str | None = (
|
|
95
|
+
os.path.join(
|
|
96
|
+
self.meta.local_location, os.path.basename(self.meta.dst_file_name)
|
|
97
|
+
)
|
|
98
|
+
if self.meta.local_location
|
|
99
|
+
else None
|
|
100
|
+
)
|
|
101
|
+
self.intermediate_dst_path: Path | None = (
|
|
102
|
+
Path(self.full_dst_file_name + ".part")
|
|
103
|
+
if self.meta.local_location
|
|
104
|
+
else None
|
|
105
|
+
)
|
|
106
|
+
# CHUNK
|
|
107
|
+
self.chunked_transfer = chunked_transfer # only true for GCS
|
|
108
|
+
self.chunk_size = chunk_size
|
|
109
|
+
self.num_of_chunks = 0
|
|
110
|
+
self.lock = threading.Lock()
|
|
111
|
+
self.successful_transfers: int = 0
|
|
112
|
+
self.failed_transfers: int = 0
|
|
113
|
+
# only used when PRESIGNED_URL expires
|
|
114
|
+
self.last_err_is_presigned_url = False
|
|
115
|
+
self.unsafe_file_write = unsafe_file_write
|
|
116
|
+
|
|
117
|
+
def compress(self) -> None:
|
|
118
|
+
if self.meta.require_compress:
|
|
119
|
+
meta = self.meta
|
|
120
|
+
logger.debug(f"compressing file={meta.src_file_name}")
|
|
121
|
+
if meta.intermediate_stream:
|
|
122
|
+
(
|
|
123
|
+
meta.src_stream,
|
|
124
|
+
upload_size,
|
|
125
|
+
) = SnowflakeFileUtil.compress_with_gzip_from_stream(
|
|
126
|
+
meta.intermediate_stream
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
(
|
|
130
|
+
meta.real_src_file_name,
|
|
131
|
+
upload_size,
|
|
132
|
+
) = SnowflakeFileUtil.compress_file_with_gzip(
|
|
133
|
+
meta.src_file_name, self.tmp_dir
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def get_digest(self) -> None:
|
|
137
|
+
meta = self.meta
|
|
138
|
+
logger.debug(f"getting digest file={meta.real_src_file_name}")
|
|
139
|
+
if meta.intermediate_stream is None:
|
|
140
|
+
(
|
|
141
|
+
meta.sha256_digest,
|
|
142
|
+
meta.upload_size,
|
|
143
|
+
) = SnowflakeFileUtil.get_digest_and_size_for_file(meta.real_src_file_name)
|
|
144
|
+
else:
|
|
145
|
+
(
|
|
146
|
+
meta.sha256_digest,
|
|
147
|
+
meta.upload_size,
|
|
148
|
+
) = SnowflakeFileUtil.get_digest_and_size_for_stream(
|
|
149
|
+
meta.src_stream or meta.intermediate_stream
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def encrypt(self) -> None:
|
|
153
|
+
meta = self.meta
|
|
154
|
+
logger.debug(f"encrypting file={meta.real_src_file_name}")
|
|
155
|
+
if meta.intermediate_stream is None:
|
|
156
|
+
(
|
|
157
|
+
self.encryption_metadata,
|
|
158
|
+
self.data_file,
|
|
159
|
+
) = SnowflakeEncryptionUtil.encrypt_file(
|
|
160
|
+
meta.encryption_material,
|
|
161
|
+
meta.real_src_file_name,
|
|
162
|
+
tmp_dir=self.tmp_dir,
|
|
163
|
+
)
|
|
164
|
+
meta.upload_size = os.path.getsize(self.data_file)
|
|
165
|
+
else:
|
|
166
|
+
encrypted_stream = BytesIO()
|
|
167
|
+
src_stream = meta.src_stream or meta.intermediate_stream
|
|
168
|
+
src_stream.seek(0)
|
|
169
|
+
self.encryption_metadata = SnowflakeEncryptionUtil.encrypt_stream(
|
|
170
|
+
meta.encryption_material, src_stream, encrypted_stream
|
|
171
|
+
)
|
|
172
|
+
src_stream.seek(0)
|
|
173
|
+
meta.upload_size = encrypted_stream.seek(0, os.SEEK_END)
|
|
174
|
+
encrypted_stream.seek(0)
|
|
175
|
+
if meta.src_stream is not None:
|
|
176
|
+
meta.src_stream.close()
|
|
177
|
+
meta.src_stream = encrypted_stream
|
|
178
|
+
self.data_file = meta.real_src_file_name
|
|
179
|
+
|
|
180
|
+
@abstractmethod
|
|
181
|
+
def get_file_header(self, filename: str) -> FileHeader | None:
|
|
182
|
+
"""Check if file exists in target location and obtain file metadata if exists.
|
|
183
|
+
|
|
184
|
+
Notes:
|
|
185
|
+
Updates meta.result_status.
|
|
186
|
+
"""
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
def preprocess(self) -> None:
|
|
190
|
+
meta = self.meta
|
|
191
|
+
logger.debug(f"Preprocessing {meta.src_file_name}")
|
|
192
|
+
|
|
193
|
+
file_header = self.get_file_header(
|
|
194
|
+
meta.dst_file_name
|
|
195
|
+
) # check if file exists on remote
|
|
196
|
+
if not meta.overwrite:
|
|
197
|
+
self.get_digest() # self.get_file_header needs digest for multiparts upload when aws is used.
|
|
198
|
+
if meta.result_status == ResultStatus.UPLOADED:
|
|
199
|
+
# Skipped
|
|
200
|
+
logger.debug(
|
|
201
|
+
f'file already exists location="{self.stage_info["location"]}", '
|
|
202
|
+
f'file_name="{meta.dst_file_name}"'
|
|
203
|
+
)
|
|
204
|
+
meta.dst_file_size = 0
|
|
205
|
+
meta.result_status = ResultStatus.SKIPPED
|
|
206
|
+
self.preprocessed = True
|
|
207
|
+
return
|
|
208
|
+
# Uploading
|
|
209
|
+
if meta.require_compress:
|
|
210
|
+
self.compress()
|
|
211
|
+
self.get_digest()
|
|
212
|
+
|
|
213
|
+
if (
|
|
214
|
+
meta.skip_upload_on_content_match
|
|
215
|
+
and file_header
|
|
216
|
+
and meta.sha256_digest == file_header.digest
|
|
217
|
+
):
|
|
218
|
+
logger.debug(f"same file contents for {meta.name}, skipping upload")
|
|
219
|
+
meta.result_status = ResultStatus.SKIPPED
|
|
220
|
+
|
|
221
|
+
self.preprocessed = True
|
|
222
|
+
|
|
223
|
+
def prepare_upload(self) -> None:
|
|
224
|
+
meta = self.meta
|
|
225
|
+
|
|
226
|
+
if not self.preprocessed:
|
|
227
|
+
self.preprocess()
|
|
228
|
+
elif meta.encryption_material:
|
|
229
|
+
# need to clean up previous encrypted file
|
|
230
|
+
os.remove(self.data_file)
|
|
231
|
+
|
|
232
|
+
logger.debug(f"Preparing to upload {meta.src_file_name}")
|
|
233
|
+
|
|
234
|
+
if meta.encryption_material:
|
|
235
|
+
self.encrypt()
|
|
236
|
+
else:
|
|
237
|
+
self.data_file = meta.real_src_file_name
|
|
238
|
+
logger.debug("finished preprocessing")
|
|
239
|
+
if meta.upload_size < meta.multipart_threshold or not self.chunked_transfer:
|
|
240
|
+
self.num_of_chunks = 1
|
|
241
|
+
else:
|
|
242
|
+
self.num_of_chunks = ceil(meta.upload_size / self.chunk_size)
|
|
243
|
+
logger.debug(f"number of chunks {self.num_of_chunks}")
|
|
244
|
+
# clean up
|
|
245
|
+
self.retry_count = {}
|
|
246
|
+
|
|
247
|
+
for chunk_id in range(self.num_of_chunks):
|
|
248
|
+
self.retry_count[chunk_id] = 0
|
|
249
|
+
if self.chunked_transfer and self.num_of_chunks > 1:
|
|
250
|
+
self._initiate_multipart_upload()
|
|
251
|
+
|
|
252
|
+
def finish_upload(self) -> None:
|
|
253
|
+
meta = self.meta
|
|
254
|
+
if self.successful_transfers == self.num_of_chunks and self.num_of_chunks != 0:
|
|
255
|
+
if self.num_of_chunks > 1:
|
|
256
|
+
self._complete_multipart_upload()
|
|
257
|
+
meta.result_status = ResultStatus.UPLOADED
|
|
258
|
+
meta.dst_file_size = meta.upload_size
|
|
259
|
+
logger.debug(f"{meta.src_file_name} upload is completed.")
|
|
260
|
+
else:
|
|
261
|
+
# TODO: add more error details to result/meta
|
|
262
|
+
meta.dst_file_size = 0
|
|
263
|
+
logger.debug(f"{meta.src_file_name} upload is aborted.")
|
|
264
|
+
if self.num_of_chunks > 1:
|
|
265
|
+
self._abort_multipart_upload()
|
|
266
|
+
meta.result_status = ResultStatus.ERROR
|
|
267
|
+
|
|
268
|
+
@abstractmethod
|
|
269
|
+
def _has_expired_token(self, response: requests.Response) -> bool:
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
def _send_request_with_retry(
|
|
273
|
+
self,
|
|
274
|
+
verb: str,
|
|
275
|
+
get_request_args: Callable[[], tuple[bytes, dict[str, Any]]],
|
|
276
|
+
retry_id: int,
|
|
277
|
+
) -> requests.Response:
|
|
278
|
+
rest_call = METHODS[verb]
|
|
279
|
+
url = b""
|
|
280
|
+
conn = None
|
|
281
|
+
if self.meta.sfagent and self.meta.sfagent._cursor.connection:
|
|
282
|
+
conn = self.meta.sfagent._cursor.connection
|
|
283
|
+
|
|
284
|
+
while self.retry_count[retry_id] < self.max_retry:
|
|
285
|
+
logger.debug(f"retry #{self.retry_count[retry_id]}")
|
|
286
|
+
cur_timestamp = self.credentials.timestamp
|
|
287
|
+
url, rest_kwargs = get_request_args()
|
|
288
|
+
rest_kwargs["timeout"] = (REQUEST_CONNECTION_TIMEOUT, REQUEST_READ_TIMEOUT)
|
|
289
|
+
try:
|
|
290
|
+
if conn:
|
|
291
|
+
with conn._rest._use_requests_session(url) as session:
|
|
292
|
+
logger.debug(f"storage client request with session {session}")
|
|
293
|
+
response = session.request(verb, url, **rest_kwargs)
|
|
294
|
+
else:
|
|
295
|
+
logger.debug("storage client request with new session")
|
|
296
|
+
response = rest_call(url, **rest_kwargs)
|
|
297
|
+
|
|
298
|
+
if self._has_expired_presigned_url(response):
|
|
299
|
+
logger.debug(
|
|
300
|
+
"presigned url expired. trying to update presigned url."
|
|
301
|
+
)
|
|
302
|
+
self._update_presigned_url()
|
|
303
|
+
else:
|
|
304
|
+
self.last_err_is_presigned_url = False
|
|
305
|
+
if response.status_code in self.TRANSIENT_HTTP_ERR:
|
|
306
|
+
logger.debug(f"transient error: {response.status_code}")
|
|
307
|
+
time.sleep(
|
|
308
|
+
min(
|
|
309
|
+
# TODO should SLEEP_UNIT come from the parent
|
|
310
|
+
# SnowflakeConnection and be customizable by users?
|
|
311
|
+
(2 ** self.retry_count[retry_id]) * self.SLEEP_UNIT,
|
|
312
|
+
self.SLEEP_MAX,
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
self.retry_count[retry_id] += 1
|
|
316
|
+
elif self._has_expired_token(response):
|
|
317
|
+
logger.debug("token is expired. trying to update token")
|
|
318
|
+
self.credentials.update(cur_timestamp)
|
|
319
|
+
self.retry_count[retry_id] += 1
|
|
320
|
+
else:
|
|
321
|
+
return response
|
|
322
|
+
except self.TRANSIENT_ERRORS as e:
|
|
323
|
+
self.last_err_is_presigned_url = False
|
|
324
|
+
time.sleep(
|
|
325
|
+
min(
|
|
326
|
+
(2 ** self.retry_count[retry_id]) * self.SLEEP_UNIT,
|
|
327
|
+
self.SLEEP_MAX,
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
logger.warning(f"{verb} with url {url} failed for transient error: {e}")
|
|
331
|
+
self.retry_count[retry_id] += 1
|
|
332
|
+
else:
|
|
333
|
+
raise RequestExceedMaxRetryError(
|
|
334
|
+
f"{verb} with url {url} failed for exceeding maximum retries."
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def _open_intermediate_dst_path(self, mode):
|
|
338
|
+
if not self.intermediate_dst_path.exists():
|
|
339
|
+
self.intermediate_dst_path.touch(mode=0o600)
|
|
340
|
+
return self.intermediate_dst_path.open(mode)
|
|
341
|
+
|
|
342
|
+
def prepare_download(self) -> None:
|
|
343
|
+
# TODO: add nicer error message for when target directory is not writeable
|
|
344
|
+
# but this should be done before we get here
|
|
345
|
+
base_dir = os.path.dirname(self.full_dst_file_name)
|
|
346
|
+
if not os.path.exists(base_dir):
|
|
347
|
+
os.makedirs(base_dir)
|
|
348
|
+
|
|
349
|
+
# HEAD
|
|
350
|
+
file_header = self.get_file_header(self.meta.real_src_file_name)
|
|
351
|
+
|
|
352
|
+
if file_header and file_header.encryption_metadata:
|
|
353
|
+
self.encryption_metadata = file_header.encryption_metadata
|
|
354
|
+
|
|
355
|
+
self.num_of_chunks = 1
|
|
356
|
+
if file_header and file_header.content_length:
|
|
357
|
+
self.meta.src_file_size = file_header.content_length
|
|
358
|
+
if (
|
|
359
|
+
self.chunked_transfer
|
|
360
|
+
and self.meta.src_file_size > self.meta.multipart_threshold
|
|
361
|
+
):
|
|
362
|
+
self.num_of_chunks = ceil(file_header.content_length / self.chunk_size)
|
|
363
|
+
|
|
364
|
+
# Preallocate encrypted file.
|
|
365
|
+
with self._open_intermediate_dst_path("wb+") as fd:
|
|
366
|
+
fd.truncate(self.meta.src_file_size)
|
|
367
|
+
|
|
368
|
+
def write_downloaded_chunk(self, chunk_id: int, data: bytes) -> None:
|
|
369
|
+
"""Writes given data to the temp location starting at chunk_id * chunk_size."""
|
|
370
|
+
# TODO: should we use chunking and write content in smaller chunks?
|
|
371
|
+
with self._open_intermediate_dst_path("rb+") as fd:
|
|
372
|
+
fd.seek(self.chunk_size * chunk_id)
|
|
373
|
+
fd.write(data)
|
|
374
|
+
|
|
375
|
+
def finish_download(self) -> None:
|
|
376
|
+
meta = self.meta
|
|
377
|
+
if self.num_of_chunks != 0 and self.successful_transfers == self.num_of_chunks:
|
|
378
|
+
meta.result_status = ResultStatus.DOWNLOADED
|
|
379
|
+
if meta.encryption_material:
|
|
380
|
+
logger.debug(f"encrypted data file={self.full_dst_file_name}")
|
|
381
|
+
# For storage utils that do not have the privilege of
|
|
382
|
+
# getting the metadata early, both object and metadata
|
|
383
|
+
# are downloaded at once. In which case, the file meta will
|
|
384
|
+
# be updated with all the metadata that we need, and
|
|
385
|
+
# then we can call get_file_header to get just that and also
|
|
386
|
+
# preserve the idea of getting metadata in the first place.
|
|
387
|
+
# One example of this is the utils that use presigned url
|
|
388
|
+
# for upload/download and not the storage client library.
|
|
389
|
+
if meta.presigned_url is not None:
|
|
390
|
+
file_header = self.get_file_header(meta.src_file_name)
|
|
391
|
+
self.encryption_metadata = file_header.encryption_metadata
|
|
392
|
+
|
|
393
|
+
tmp_dst_file_name = SnowflakeEncryptionUtil.decrypt_file(
|
|
394
|
+
self.encryption_metadata,
|
|
395
|
+
meta.encryption_material,
|
|
396
|
+
str(self.intermediate_dst_path),
|
|
397
|
+
tmp_dir=self.tmp_dir,
|
|
398
|
+
unsafe_file_write=self.unsafe_file_write,
|
|
399
|
+
)
|
|
400
|
+
shutil.move(tmp_dst_file_name, self.full_dst_file_name)
|
|
401
|
+
self.intermediate_dst_path.unlink()
|
|
402
|
+
else:
|
|
403
|
+
logger.debug(f"not encrypted data file={self.full_dst_file_name}")
|
|
404
|
+
shutil.move(str(self.intermediate_dst_path), self.full_dst_file_name)
|
|
405
|
+
stat_info = os.stat(self.full_dst_file_name)
|
|
406
|
+
meta.dst_file_size = stat_info.st_size
|
|
407
|
+
else:
|
|
408
|
+
# TODO: add more error details to result/meta
|
|
409
|
+
if os.path.isfile(self.full_dst_file_name):
|
|
410
|
+
os.unlink(self.full_dst_file_name)
|
|
411
|
+
logger.exception(f"Failed to download a file: {self.full_dst_file_name}")
|
|
412
|
+
meta.dst_file_size = -1
|
|
413
|
+
meta.result_status = ResultStatus.ERROR
|
|
414
|
+
|
|
415
|
+
def upload_chunk(self, chunk_id: int) -> None:
|
|
416
|
+
new_stream = not bool(self.meta.src_stream or self.meta.intermediate_stream)
|
|
417
|
+
fd = (
|
|
418
|
+
self.meta.src_stream
|
|
419
|
+
or self.meta.intermediate_stream
|
|
420
|
+
or open(self.data_file, "rb")
|
|
421
|
+
)
|
|
422
|
+
try:
|
|
423
|
+
if self.num_of_chunks == 1:
|
|
424
|
+
_data = fd.read()
|
|
425
|
+
else:
|
|
426
|
+
fd.seek(chunk_id * self.chunk_size)
|
|
427
|
+
_data = fd.read(self.chunk_size)
|
|
428
|
+
finally:
|
|
429
|
+
if new_stream:
|
|
430
|
+
fd.close()
|
|
431
|
+
logger.debug(f"Uploading chunk {chunk_id} of file {self.data_file}")
|
|
432
|
+
self._upload_chunk(chunk_id, _data)
|
|
433
|
+
logger.debug(f"Successfully uploaded chunk {chunk_id} of file {self.data_file}")
|
|
434
|
+
|
|
435
|
+
@abstractmethod
|
|
436
|
+
def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
|
|
437
|
+
pass
|
|
438
|
+
|
|
439
|
+
@abstractmethod
|
|
440
|
+
def download_chunk(self, chunk_id: int) -> None:
|
|
441
|
+
pass
|
|
442
|
+
|
|
443
|
+
# Override in GCS
|
|
444
|
+
def _has_expired_presigned_url(self, response: requests.Response) -> bool:
|
|
445
|
+
return False
|
|
446
|
+
|
|
447
|
+
# Override in GCS
|
|
448
|
+
def _update_presigned_url(self) -> None:
|
|
449
|
+
return
|
|
450
|
+
|
|
451
|
+
# Override in S3
|
|
452
|
+
def _initiate_multipart_upload(self) -> None:
|
|
453
|
+
return
|
|
454
|
+
|
|
455
|
+
# Override in S3
|
|
456
|
+
def _complete_multipart_upload(self) -> None:
|
|
457
|
+
return
|
|
458
|
+
|
|
459
|
+
# Override in S3
|
|
460
|
+
def _abort_multipart_upload(self) -> None:
|
|
461
|
+
return
|
|
462
|
+
|
|
463
|
+
def delete_client_data(self) -> None:
|
|
464
|
+
"""Deletes the tmp_dir and closes the source stream belonging to this client.
|
|
465
|
+
This function is idempotent."""
|
|
466
|
+
if os.path.exists(self.tmp_dir):
|
|
467
|
+
logger.debug(f"cleaning up tmp dir: {self.tmp_dir}")
|
|
468
|
+
try:
|
|
469
|
+
shutil.rmtree(self.tmp_dir)
|
|
470
|
+
except OSError as ex:
|
|
471
|
+
# it's ok to ignore the exception here because another thread might
|
|
472
|
+
# have cleaned up the temp directory
|
|
473
|
+
logger.debug(f"Failed to delete {self.tmp_dir}: {ex}")
|
|
474
|
+
if self.meta.src_stream and not self.meta.src_stream.closed:
|
|
475
|
+
self.meta.src_stream.close()
|
|
476
|
+
|
|
477
|
+
def __del__(self) -> None:
|
|
478
|
+
self.delete_client_data()
|