snowflake-connector-python 4.4.0__cp314-cp314-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/connector/__init__.py +118 -0
- snowflake/connector/_query_context_cache.py +276 -0
- snowflake/connector/_sql_util.py +48 -0
- snowflake/connector/_utils.py +353 -0
- snowflake/connector/aio/README.md +88 -0
- snowflake/connector/aio/__init__.py +163 -0
- snowflake/connector/aio/_azure_storage_client.py +227 -0
- snowflake/connector/aio/_bind_upload_agent.py +69 -0
- snowflake/connector/aio/_connection.py +1216 -0
- snowflake/connector/aio/_cursor.py +1402 -0
- snowflake/connector/aio/_description.py +5 -0
- snowflake/connector/aio/_direct_file_operation_utils.py +88 -0
- snowflake/connector/aio/_file_transfer_agent.py +316 -0
- snowflake/connector/aio/_gcs_storage_client.py +360 -0
- snowflake/connector/aio/_network.py +860 -0
- snowflake/connector/aio/_ocsp_asn1crypto.py +51 -0
- snowflake/connector/aio/_ocsp_snowflake.py +608 -0
- snowflake/connector/aio/_pandas_tools.py +561 -0
- snowflake/connector/aio/_result_batch.py +442 -0
- snowflake/connector/aio/_result_set.py +309 -0
- snowflake/connector/aio/_s3_storage_client.py +460 -0
- snowflake/connector/aio/_session_manager.py +588 -0
- snowflake/connector/aio/_storage_client.py +331 -0
- snowflake/connector/aio/_telemetry.py +97 -0
- snowflake/connector/aio/_time_util.py +57 -0
- snowflake/connector/aio/_wif_util.py +324 -0
- snowflake/connector/aio/auth/__init__.py +52 -0
- snowflake/connector/aio/auth/_auth.py +406 -0
- snowflake/connector/aio/auth/_by_plugin.py +131 -0
- snowflake/connector/aio/auth/_default.py +28 -0
- snowflake/connector/aio/auth/_idtoken.py +54 -0
- snowflake/connector/aio/auth/_keypair.py +60 -0
- snowflake/connector/aio/auth/_no_auth.py +31 -0
- snowflake/connector/aio/auth/_oauth.py +27 -0
- snowflake/connector/aio/auth/_oauth_code.py +117 -0
- snowflake/connector/aio/auth/_oauth_credentials.py +100 -0
- snowflake/connector/aio/auth/_okta.py +247 -0
- snowflake/connector/aio/auth/_pat.py +27 -0
- snowflake/connector/aio/auth/_usrpwdmfa.py +30 -0
- snowflake/connector/aio/auth/_webbrowser.py +405 -0
- snowflake/connector/aio/auth/_workload_identity.py +60 -0
- snowflake/connector/arrow_context.py +211 -0
- snowflake/connector/auth/__init__.py +53 -0
- snowflake/connector/auth/_auth.py +669 -0
- snowflake/connector/auth/_http_server.py +257 -0
- snowflake/connector/auth/_oauth_base.py +472 -0
- snowflake/connector/auth/by_plugin.py +219 -0
- snowflake/connector/auth/default.py +36 -0
- snowflake/connector/auth/idtoken.py +72 -0
- snowflake/connector/auth/keypair.py +225 -0
- snowflake/connector/auth/no_auth.py +39 -0
- snowflake/connector/auth/oauth.py +49 -0
- snowflake/connector/auth/oauth_code.py +490 -0
- snowflake/connector/auth/oauth_credentials.py +71 -0
- snowflake/connector/auth/okta.py +339 -0
- snowflake/connector/auth/pat.py +40 -0
- snowflake/connector/auth/usrpwdmfa.py +69 -0
- snowflake/connector/auth/webbrowser.py +514 -0
- snowflake/connector/auth/workload_identity.py +110 -0
- snowflake/connector/azure_storage_client.py +278 -0
- snowflake/connector/backoff_policies.py +141 -0
- snowflake/connector/bind_upload_agent.py +89 -0
- snowflake/connector/cache.py +696 -0
- snowflake/connector/compat.py +127 -0
- snowflake/connector/config_manager.py +553 -0
- snowflake/connector/connection.py +2671 -0
- snowflake/connector/connection_diagnostic.py +793 -0
- snowflake/connector/constants.py +460 -0
- snowflake/connector/converter.py +809 -0
- snowflake/connector/converter_issue23517.py +87 -0
- snowflake/connector/converter_null.py +14 -0
- snowflake/connector/converter_snowsql.py +205 -0
- snowflake/connector/crl.py +919 -0
- snowflake/connector/crl_cache.py +734 -0
- snowflake/connector/cursor.py +2099 -0
- snowflake/connector/dbapi.py +53 -0
- snowflake/connector/description.py +21 -0
- snowflake/connector/direct_file_operation_utils.py +88 -0
- snowflake/connector/encryption_util.py +220 -0
- snowflake/connector/errorcode.py +94 -0
- snowflake/connector/errors.py +666 -0
- snowflake/connector/externals_utils/__init__.py +0 -0
- snowflake/connector/externals_utils/externals_setup.py +30 -0
- snowflake/connector/feature.py +4 -0
- snowflake/connector/file_compression_type.py +118 -0
- snowflake/connector/file_lock.py +72 -0
- snowflake/connector/file_transfer_agent.py +1244 -0
- snowflake/connector/file_util.py +153 -0
- snowflake/connector/gcs_storage_client.py +476 -0
- snowflake/connector/gzip_decoder.py +85 -0
- snowflake/connector/interval_util.py +26 -0
- snowflake/connector/local_storage_client.py +90 -0
- snowflake/connector/log_configuration.py +62 -0
- snowflake/connector/logging_utils/__init__.py +0 -0
- snowflake/connector/logging_utils/filters.py +72 -0
- snowflake/connector/minicore/__init__.py +0 -0
- snowflake/connector/minicore/aix_ppc64/libsf_mini_core.a +0 -0
- snowflake/connector/minicore/aix_ppc64/libsf_mini_core_static.a +0 -0
- snowflake/connector/minicore/linux_aarch64_glibc/libsf_mini_core.so +0 -0
- snowflake/connector/minicore/linux_aarch64_musl/libsf_mini_core.so +0 -0
- snowflake/connector/minicore/linux_x86_64_glibc/libsf_mini_core.so +0 -0
- snowflake/connector/minicore/linux_x86_64_musl/libsf_mini_core.so +0 -0
- snowflake/connector/minicore/macos_aarch64/libsf_mini_core.dylib +0 -0
- snowflake/connector/minicore/macos_x86_64/libsf_mini_core.dylib +0 -0
- snowflake/connector/minicore/windows_x86_64/sf_mini_core.dll +0 -0
- snowflake/connector/minicore/windows_x86_64/sf_mini_core_static.lib +0 -0
- snowflake/connector/nanoarrow_arrow_iterator.cp314-win_amd64.pyd +0 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.cpp +60 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.cpp +21 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.hpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.cpp +570 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.hpp +98 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.cpp +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.hpp +115 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.cpp +1140 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.hpp +138 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.cpp +47 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.hpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.cpp +83 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.cpp +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.hpp +72 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.cpp +73 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.hpp +28 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.cpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IColumnConverter.hpp +17 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.cpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.hpp +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.cpp +75 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.hpp +57 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/LICENSE.txt +209 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.cpp +75 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.hpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.cpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.cpp +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.hpp +95 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.cpp +57 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.hpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.cpp +34 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.hpp +48 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.cpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.hpp +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.cpp +346 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.hpp +145 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/macros.hpp +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.cpp +67 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.hpp +68 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_accessors.h +101 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_alloc.h +127 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_assert.h +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_builder.h +1908 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_emitter.h +215 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_endian.h +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_epilogue.h +7 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_flatbuffers.h +55 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_identifier.h +148 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_iov.h +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_prologue.h +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_refmap.h +144 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_rtconfig.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_types.h +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_verifier.h +239 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/flatcc_portable.h +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/paligned_alloc.h +210 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pattributes.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_pop.h +20 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_push.h +51 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian.h +206 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian_detect.h +118 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinline.h +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinttypes.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable.h +2 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable_basic.h +25 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstatic_assert.h +67 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdalign.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdint.h +898 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/punaligned.h +190 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pversion.h +6 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pwarnings.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc.c +3204 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.c +3217 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.h +3618 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.hpp +379 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_arrow_iterator.pyx +262 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.c +512 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.h +350 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.c +33273 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.h +397 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.cpp +100 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.hpp +49 -0
- snowflake/connector/network.py +1227 -0
- snowflake/connector/ocsp_asn1crypto.py +447 -0
- snowflake/connector/ocsp_snowflake.py +1939 -0
- snowflake/connector/options.py +179 -0
- snowflake/connector/os_details.py +88 -0
- snowflake/connector/pandas_tools.py +734 -0
- snowflake/connector/platform_detection.py +555 -0
- snowflake/connector/proxy.py +28 -0
- snowflake/connector/py.typed +0 -0
- snowflake/connector/result_batch.py +917 -0
- snowflake/connector/result_set.py +340 -0
- snowflake/connector/s3_storage_client.py +614 -0
- snowflake/connector/secret_detector.py +181 -0
- snowflake/connector/session_manager.py +650 -0
- snowflake/connector/sf_dirs.py +64 -0
- snowflake/connector/sfbinaryformat.py +35 -0
- snowflake/connector/sfdatetime.py +345 -0
- snowflake/connector/snow_logging.py +144 -0
- snowflake/connector/sqlstate.py +9 -0
- snowflake/connector/ssd_internal_keys.py +30 -0
- snowflake/connector/ssl_wrap_socket.py +281 -0
- snowflake/connector/storage_client.py +486 -0
- snowflake/connector/telemetry.py +254 -0
- snowflake/connector/telemetry_oob.py +544 -0
- snowflake/connector/test_util.py +30 -0
- snowflake/connector/time_util.py +159 -0
- snowflake/connector/token_cache.py +457 -0
- snowflake/connector/tool/__init__.py +0 -0
- snowflake/connector/tool/dump_certs.py +57 -0
- snowflake/connector/tool/dump_ocsp_response.py +139 -0
- snowflake/connector/tool/dump_ocsp_response_cache.py +194 -0
- snowflake/connector/tool/probe_connection.py +69 -0
- snowflake/connector/url_util.py +55 -0
- snowflake/connector/util_text.py +320 -0
- snowflake/connector/vendored/__init__.py +3 -0
- snowflake/connector/vendored/requests/LICENSE +175 -0
- snowflake/connector/vendored/requests/__init__.py +184 -0
- snowflake/connector/vendored/requests/__version__.py +14 -0
- snowflake/connector/vendored/requests/_internal_utils.py +50 -0
- snowflake/connector/vendored/requests/adapters.py +696 -0
- snowflake/connector/vendored/requests/api.py +157 -0
- snowflake/connector/vendored/requests/auth.py +314 -0
- snowflake/connector/vendored/requests/certs.py +17 -0
- snowflake/connector/vendored/requests/compat.py +106 -0
- snowflake/connector/vendored/requests/cookies.py +561 -0
- snowflake/connector/vendored/requests/exceptions.py +151 -0
- snowflake/connector/vendored/requests/help.py +134 -0
- snowflake/connector/vendored/requests/hooks.py +33 -0
- snowflake/connector/vendored/requests/models.py +1039 -0
- snowflake/connector/vendored/requests/packages.py +23 -0
- snowflake/connector/vendored/requests/sessions.py +831 -0
- snowflake/connector/vendored/requests/status_codes.py +128 -0
- snowflake/connector/vendored/requests/structures.py +99 -0
- snowflake/connector/vendored/requests/utils.py +1086 -0
- snowflake/connector/vendored/urllib3/LICENSE.txt +21 -0
- snowflake/connector/vendored/urllib3/__init__.py +211 -0
- snowflake/connector/vendored/urllib3/_base_connection.py +165 -0
- snowflake/connector/vendored/urllib3/_collections.py +487 -0
- snowflake/connector/vendored/urllib3/_request_methods.py +278 -0
- snowflake/connector/vendored/urllib3/_version.py +34 -0
- snowflake/connector/vendored/urllib3/connection.py +1108 -0
- snowflake/connector/vendored/urllib3/connectionpool.py +1178 -0
- snowflake/connector/vendored/urllib3/contrib/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/__init__.py +17 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/connection.py +260 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/emscripten_fetch_worker.js +110 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/fetch.py +726 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/request.py +22 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/response.py +277 -0
- snowflake/connector/vendored/urllib3/contrib/pyopenssl.py +564 -0
- snowflake/connector/vendored/urllib3/contrib/socks.py +228 -0
- snowflake/connector/vendored/urllib3/exceptions.py +335 -0
- snowflake/connector/vendored/urllib3/fields.py +341 -0
- snowflake/connector/vendored/urllib3/filepost.py +89 -0
- snowflake/connector/vendored/urllib3/http2/__init__.py +53 -0
- snowflake/connector/vendored/urllib3/http2/connection.py +356 -0
- snowflake/connector/vendored/urllib3/http2/probe.py +87 -0
- snowflake/connector/vendored/urllib3/poolmanager.py +651 -0
- snowflake/connector/vendored/urllib3/py.typed +2 -0
- snowflake/connector/vendored/urllib3/response.py +1480 -0
- snowflake/connector/vendored/urllib3/util/__init__.py +42 -0
- snowflake/connector/vendored/urllib3/util/connection.py +137 -0
- snowflake/connector/vendored/urllib3/util/proxy.py +43 -0
- snowflake/connector/vendored/urllib3/util/request.py +263 -0
- snowflake/connector/vendored/urllib3/util/response.py +101 -0
- snowflake/connector/vendored/urllib3/util/retry.py +549 -0
- snowflake/connector/vendored/urllib3/util/ssl_.py +527 -0
- snowflake/connector/vendored/urllib3/util/ssl_match_hostname.py +159 -0
- snowflake/connector/vendored/urllib3/util/ssltransport.py +271 -0
- snowflake/connector/vendored/urllib3/util/timeout.py +275 -0
- snowflake/connector/vendored/urllib3/util/url.py +469 -0
- snowflake/connector/vendored/urllib3/util/util.py +42 -0
- snowflake/connector/vendored/urllib3/util/wait.py +124 -0
- snowflake/connector/version.py +3 -0
- snowflake/connector/wif_util.py +434 -0
- snowflake_connector_python-4.4.0.dist-info/DELVEWHEEL +2 -0
- snowflake_connector_python-4.4.0.dist-info/METADATA +1591 -0
- snowflake_connector_python-4.4.0.dist-info/RECORD +302 -0
- snowflake_connector_python-4.4.0.dist-info/WHEEL +5 -0
- snowflake_connector_python-4.4.0.dist-info/entry_points.txt +4 -0
- snowflake_connector_python-4.4.0.dist-info/licenses/LICENSE.txt +202 -0
- snowflake_connector_python-4.4.0.dist-info/licenses/NOTICE +8 -0
- snowflake_connector_python-4.4.0.dist-info/top_level.txt +1 -0
- snowflake_connector_python.libs/msvcp140-0f885b509a685d2bbfa652fed26b5fb3.dll +0 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import binascii
|
|
4
|
+
import re
|
|
5
|
+
import xml.etree.ElementTree as ET
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from io import IOBase
|
|
8
|
+
from logging import getLogger
|
|
9
|
+
from operator import itemgetter
|
|
10
|
+
from typing import TYPE_CHECKING, Any, NamedTuple
|
|
11
|
+
|
|
12
|
+
from cryptography.hazmat.primitives import hashes, hmac
|
|
13
|
+
|
|
14
|
+
from .compat import quote, urlparse
|
|
15
|
+
from .constants import (
|
|
16
|
+
HTTP_HEADER_CONTENT_TYPE,
|
|
17
|
+
HTTP_HEADER_VALUE_OCTET_STREAM,
|
|
18
|
+
FileHeader,
|
|
19
|
+
ResultStatus,
|
|
20
|
+
)
|
|
21
|
+
from .encryption_util import EncryptionMetadata
|
|
22
|
+
from .storage_client import SnowflakeStorageClient, remove_content_encoding
|
|
23
|
+
from .vendored import requests
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
26
|
+
from .file_transfer_agent import SnowflakeFileMeta, StorageCredential
|
|
27
|
+
|
|
28
|
+
logger = getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
META_PREFIX = "x-amz-meta-"
|
|
31
|
+
SFC_DIGEST = "sfc-digest"
|
|
32
|
+
|
|
33
|
+
AMZ_MATDESC = "x-amz-matdesc"
|
|
34
|
+
AMZ_KEY = "x-amz-key"
|
|
35
|
+
AMZ_IV = "x-amz-iv"
|
|
36
|
+
|
|
37
|
+
ERRORNO_WSAECONNABORTED = 10053 # network connection was aborted
|
|
38
|
+
|
|
39
|
+
EXPIRED_TOKEN = "ExpiredToken"
|
|
40
|
+
ADDRESSING_STYLE = "virtual" # explicit force to use virtual addressing style
|
|
41
|
+
UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"
|
|
42
|
+
|
|
43
|
+
RE_MULTIPLE_SPACES = re.compile(r" +")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class S3Location(NamedTuple):
|
|
47
|
+
bucket_name: str
|
|
48
|
+
path: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SnowflakeS3RestClient(SnowflakeStorageClient):
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
meta: SnowflakeFileMeta,
|
|
55
|
+
credentials: StorageCredential,
|
|
56
|
+
stage_info: dict[str, Any],
|
|
57
|
+
chunk_size: int,
|
|
58
|
+
use_accelerate_endpoint: bool | None = None,
|
|
59
|
+
use_s3_regional_url: bool = False,
|
|
60
|
+
unsafe_file_write: bool = False,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Rest client for S3 storage.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
stage_info:
|
|
66
|
+
"""
|
|
67
|
+
super().__init__(
|
|
68
|
+
meta,
|
|
69
|
+
stage_info,
|
|
70
|
+
chunk_size,
|
|
71
|
+
credentials=credentials,
|
|
72
|
+
unsafe_file_write=unsafe_file_write,
|
|
73
|
+
)
|
|
74
|
+
# Signature version V4
|
|
75
|
+
# Addressing style Virtual Host
|
|
76
|
+
self.region_name: str = stage_info["region"]
|
|
77
|
+
# Multipart upload only
|
|
78
|
+
self.upload_id: str | None = None
|
|
79
|
+
self.etags: list[str] | None = None
|
|
80
|
+
self.s3location: S3Location = (
|
|
81
|
+
SnowflakeS3RestClient._extract_bucket_name_and_path(
|
|
82
|
+
self.stage_info["location"]
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
self.use_s3_regional_url = (
|
|
86
|
+
use_s3_regional_url
|
|
87
|
+
or "useS3RegionalUrl" in stage_info
|
|
88
|
+
and stage_info["useS3RegionalUrl"]
|
|
89
|
+
or "useRegionalUrl" in stage_info
|
|
90
|
+
and stage_info["useRegionalUrl"]
|
|
91
|
+
)
|
|
92
|
+
self.location_type = stage_info.get("locationType")
|
|
93
|
+
|
|
94
|
+
# if GS sends us an endpoint, it's likely for FIPS. Use it.
|
|
95
|
+
self.endpoint: str | None = None
|
|
96
|
+
if stage_info["endPoint"]:
|
|
97
|
+
self.endpoint = (
|
|
98
|
+
f"https://{self.s3location.bucket_name}." + stage_info["endPoint"]
|
|
99
|
+
)
|
|
100
|
+
self.transfer_accelerate_config(use_accelerate_endpoint)
|
|
101
|
+
|
|
102
|
+
def transfer_accelerate_config(
|
|
103
|
+
self, use_accelerate_endpoint: bool | None = None
|
|
104
|
+
) -> bool:
|
|
105
|
+
# accelerate cannot be used in China and us government
|
|
106
|
+
if self.region_name and self.region_name.startswith("cn-"):
|
|
107
|
+
self.endpoint = (
|
|
108
|
+
f"https://{self.s3location.bucket_name}."
|
|
109
|
+
f"s3.{self.region_name}.amazonaws.com.cn"
|
|
110
|
+
)
|
|
111
|
+
return False
|
|
112
|
+
# if self.endpoint has been set, e.g. by metadata, no more config is needed.
|
|
113
|
+
if self.endpoint is not None:
|
|
114
|
+
return self.endpoint.find("s3-accelerate.amazonaws.com") >= 0
|
|
115
|
+
if self.use_s3_regional_url:
|
|
116
|
+
self.endpoint = (
|
|
117
|
+
f"https://{self.s3location.bucket_name}."
|
|
118
|
+
f"s3.{self.region_name}.amazonaws.com"
|
|
119
|
+
)
|
|
120
|
+
return False
|
|
121
|
+
else:
|
|
122
|
+
if use_accelerate_endpoint is None:
|
|
123
|
+
if str(self.s3location.bucket_name).lower().startswith("sfc-"):
|
|
124
|
+
# SNOW-2324060: no s3:GetAccelerateConfiguration and no intention to add either
|
|
125
|
+
# for internal stage, thus previously the client got HTTP403 on /accelerate call
|
|
126
|
+
logger.debug(
|
|
127
|
+
"Not attempting to get bucket transfer accelerate endpoint for internal stage."
|
|
128
|
+
)
|
|
129
|
+
use_accelerate_endpoint = False
|
|
130
|
+
else:
|
|
131
|
+
use_accelerate_endpoint = self._get_bucket_accelerate_config(
|
|
132
|
+
self.s3location.bucket_name
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if use_accelerate_endpoint:
|
|
136
|
+
self.endpoint = (
|
|
137
|
+
f"https://{self.s3location.bucket_name}.s3-accelerate.amazonaws.com"
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
self.endpoint = (
|
|
141
|
+
f"https://{self.s3location.bucket_name}.s3.amazonaws.com"
|
|
142
|
+
)
|
|
143
|
+
logger.debug(f"Using {self.endpoint} as storage endpoint.")
|
|
144
|
+
return use_accelerate_endpoint
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def _sign_bytes(secret_key: bytes, _input: str) -> bytes:
|
|
148
|
+
"""Applies HMAC-SHA-256 to given string with secret_key."""
|
|
149
|
+
h = hmac.HMAC(secret_key, hashes.SHA256())
|
|
150
|
+
h.update(_input.encode("utf-8"))
|
|
151
|
+
return h.finalize()
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def _sign_bytes_hex(secret_key: bytes, _input: str) -> bytes:
|
|
155
|
+
"""Convenience function, same as _sign_bytes, but returns result in hex form."""
|
|
156
|
+
return binascii.hexlify(SnowflakeS3RestClient._sign_bytes(secret_key, _input))
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def _hash_bytes(_input: bytes) -> bytes:
|
|
160
|
+
"""Applies SHA-256 hash to given bytes."""
|
|
161
|
+
digest = hashes.Hash(hashes.SHA256())
|
|
162
|
+
digest.update(_input)
|
|
163
|
+
return digest.finalize()
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def _hash_bytes_hex(_input: bytes) -> bytes:
|
|
167
|
+
"""Convenience function, same as _hash_bytes, but returns result in hex form."""
|
|
168
|
+
return binascii.hexlify(SnowflakeS3RestClient._hash_bytes(_input))
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def _construct_query_string(
|
|
172
|
+
query_parts: tuple[tuple[str, str], ...],
|
|
173
|
+
) -> str:
|
|
174
|
+
"""Convenience function to build the query part of a URL from key-value pairs.
|
|
175
|
+
|
|
176
|
+
It filters out empty strings from the key, value pairs.
|
|
177
|
+
"""
|
|
178
|
+
return "&".join(["=".join(filter(bool, e)) for e in query_parts])
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def _construct_canonicalized_and_signed_headers(
|
|
182
|
+
headers: dict[str, str | list[str]]
|
|
183
|
+
) -> tuple[str, str]:
|
|
184
|
+
"""Construct canonical headers as per AWS specs, returns the signed headers too.
|
|
185
|
+
|
|
186
|
+
Does not support sorting by values in case the keys are the same, don't send
|
|
187
|
+
in duplicate keys, but this is not possible with a dictionary anyways.
|
|
188
|
+
"""
|
|
189
|
+
res = []
|
|
190
|
+
low_key_dict = {k.lower(): v for k, v in headers.items()}
|
|
191
|
+
sorted_headers = sorted(low_key_dict.keys())
|
|
192
|
+
_res = [(k, low_key_dict[k]) for k in sorted_headers]
|
|
193
|
+
|
|
194
|
+
for k, v in _res:
|
|
195
|
+
# if value is a list, convert to string delimited by comma
|
|
196
|
+
if isinstance(v, list):
|
|
197
|
+
v = ",".join(v)
|
|
198
|
+
# if multiline header, replace withs space
|
|
199
|
+
k = k.replace("\n", " ")
|
|
200
|
+
res.append(k.strip() + ":" + RE_MULTIPLE_SPACES.sub(" ", v.strip()))
|
|
201
|
+
|
|
202
|
+
ans = "\n".join(res)
|
|
203
|
+
if ans:
|
|
204
|
+
ans += "\n"
|
|
205
|
+
|
|
206
|
+
return ans, ";".join(sorted_headers)
|
|
207
|
+
|
|
208
|
+
@staticmethod
|
|
209
|
+
def _construct_canonical_request_and_signed_headers(
|
|
210
|
+
verb: str,
|
|
211
|
+
canonical_uri_parameter: str,
|
|
212
|
+
query_parts: dict[str, str],
|
|
213
|
+
canonical_headers: dict[str, str | list[str]] | None = None,
|
|
214
|
+
payload_hash: str = "",
|
|
215
|
+
) -> tuple[str, str]:
|
|
216
|
+
"""Build canonical request and also return signed headers.
|
|
217
|
+
|
|
218
|
+
Note: this doesn't support sorting by values in case the same key is given
|
|
219
|
+
more than once, but doing this is also not possible with a dictionary.
|
|
220
|
+
"""
|
|
221
|
+
canonical_query_string = "&".join(
|
|
222
|
+
"=".join([k, v]) for k, v in sorted(query_parts.items(), key=itemgetter(0))
|
|
223
|
+
)
|
|
224
|
+
(
|
|
225
|
+
canonical_headers,
|
|
226
|
+
signed_headers,
|
|
227
|
+
) = SnowflakeS3RestClient._construct_canonicalized_and_signed_headers(
|
|
228
|
+
canonical_headers
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
"\n".join(
|
|
233
|
+
[
|
|
234
|
+
verb,
|
|
235
|
+
canonical_uri_parameter or "/",
|
|
236
|
+
canonical_query_string,
|
|
237
|
+
canonical_headers,
|
|
238
|
+
signed_headers,
|
|
239
|
+
payload_hash,
|
|
240
|
+
]
|
|
241
|
+
),
|
|
242
|
+
signed_headers,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
@staticmethod
|
|
246
|
+
def _construct_string_to_sign(
|
|
247
|
+
region_name: str,
|
|
248
|
+
service_name: str,
|
|
249
|
+
amzdate: str,
|
|
250
|
+
short_amzdate: str,
|
|
251
|
+
canonical_request_hash: bytes,
|
|
252
|
+
) -> tuple[str, str]:
|
|
253
|
+
"""Given all the necessary information construct a V4 string to sign.
|
|
254
|
+
|
|
255
|
+
As per AWS specs it requires the scope, the hash of the canonical request and
|
|
256
|
+
the current date in the following format: YYYYMMDDTHHMMSSZ where T and Z are
|
|
257
|
+
constant characters.
|
|
258
|
+
This function generates the scope from the amzdate (which is just the date
|
|
259
|
+
portion of amzdate), region name and service we want to use (this is only s3
|
|
260
|
+
in our case).
|
|
261
|
+
"""
|
|
262
|
+
scope = f"{short_amzdate}/{region_name}/{service_name}/aws4_request"
|
|
263
|
+
return (
|
|
264
|
+
"\n".join(
|
|
265
|
+
[
|
|
266
|
+
"AWS4-HMAC-SHA256",
|
|
267
|
+
amzdate,
|
|
268
|
+
scope,
|
|
269
|
+
canonical_request_hash.decode("utf-8"),
|
|
270
|
+
]
|
|
271
|
+
),
|
|
272
|
+
scope,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def _has_expired_token(self, response: requests.Response) -> bool:
|
|
276
|
+
"""Extract error code and error message from the S3's error response.
|
|
277
|
+
|
|
278
|
+
Expected format:
|
|
279
|
+
https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#RESTErrorResponses
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
response: Rest error response in XML format
|
|
283
|
+
|
|
284
|
+
Returns: True if the error response is caused by token expiration
|
|
285
|
+
|
|
286
|
+
"""
|
|
287
|
+
if response.status_code != 400:
|
|
288
|
+
return False
|
|
289
|
+
message = response.text
|
|
290
|
+
if not message or message.isspace():
|
|
291
|
+
return False
|
|
292
|
+
err = ET.fromstring(message)
|
|
293
|
+
return err.find("Code").text == EXPIRED_TOKEN
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def _extract_bucket_name_and_path(stage_location) -> S3Location:
|
|
297
|
+
# split stage location as bucket name and path
|
|
298
|
+
bucket_name, _, path = stage_location.partition("/")
|
|
299
|
+
if path and not path.endswith("/"):
|
|
300
|
+
path += "/"
|
|
301
|
+
|
|
302
|
+
return S3Location(bucket_name=bucket_name, path=path)
|
|
303
|
+
|
|
304
|
+
def _send_request_with_authentication_and_retry(
|
|
305
|
+
self,
|
|
306
|
+
url: str,
|
|
307
|
+
verb: str,
|
|
308
|
+
retry_id: int | str,
|
|
309
|
+
query_parts: dict[str, str] | None = None,
|
|
310
|
+
x_amz_headers: dict[str, str] | None = None,
|
|
311
|
+
headers: dict[str, str] | None = None,
|
|
312
|
+
payload: bytes | bytearray | IOBase | None = None,
|
|
313
|
+
unsigned_payload: bool = False,
|
|
314
|
+
ignore_content_encoding: bool = False,
|
|
315
|
+
) -> requests.Response:
|
|
316
|
+
if x_amz_headers is None:
|
|
317
|
+
x_amz_headers = {}
|
|
318
|
+
if headers is None:
|
|
319
|
+
headers = {}
|
|
320
|
+
if payload is None:
|
|
321
|
+
payload = b""
|
|
322
|
+
if query_parts is None:
|
|
323
|
+
query_parts = {}
|
|
324
|
+
parsed_url = urlparse(url)
|
|
325
|
+
x_amz_headers["x-amz-security-token"] = self.credentials.creds.get(
|
|
326
|
+
"AWS_TOKEN", ""
|
|
327
|
+
)
|
|
328
|
+
x_amz_headers["host"] = parsed_url.hostname
|
|
329
|
+
if unsigned_payload:
|
|
330
|
+
x_amz_headers["x-amz-content-sha256"] = UNSIGNED_PAYLOAD
|
|
331
|
+
else:
|
|
332
|
+
x_amz_headers["x-amz-content-sha256"] = (
|
|
333
|
+
SnowflakeS3RestClient._hash_bytes_hex(payload).lower().decode()
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def generate_authenticated_url_and_args_v4() -> tuple[bytes, dict[str, bytes]]:
|
|
337
|
+
t = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
338
|
+
amzdate = t.strftime("%Y%m%dT%H%M%SZ")
|
|
339
|
+
short_amzdate = amzdate[:8]
|
|
340
|
+
x_amz_headers["x-amz-date"] = amzdate
|
|
341
|
+
x_amz_headers["x-amz-security-token"] = self.credentials.creds.get(
|
|
342
|
+
"AWS_TOKEN", ""
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
(
|
|
346
|
+
canonical_request,
|
|
347
|
+
signed_headers,
|
|
348
|
+
) = self._construct_canonical_request_and_signed_headers(
|
|
349
|
+
verb=verb,
|
|
350
|
+
canonical_uri_parameter=parsed_url.path
|
|
351
|
+
+ (f";{parsed_url.params}" if parsed_url.params else ""),
|
|
352
|
+
query_parts=query_parts,
|
|
353
|
+
canonical_headers=x_amz_headers,
|
|
354
|
+
payload_hash=x_amz_headers["x-amz-content-sha256"],
|
|
355
|
+
)
|
|
356
|
+
string_to_sign, scope = self._construct_string_to_sign(
|
|
357
|
+
self.region_name,
|
|
358
|
+
"s3",
|
|
359
|
+
amzdate,
|
|
360
|
+
short_amzdate,
|
|
361
|
+
self._hash_bytes_hex(canonical_request.encode("utf-8")).lower(),
|
|
362
|
+
)
|
|
363
|
+
kDate = self._sign_bytes(
|
|
364
|
+
("AWS4" + self.credentials.creds["AWS_SECRET_KEY"]).encode("utf-8"),
|
|
365
|
+
short_amzdate,
|
|
366
|
+
)
|
|
367
|
+
kRegion = self._sign_bytes(kDate, self.region_name)
|
|
368
|
+
kService = self._sign_bytes(kRegion, "s3")
|
|
369
|
+
signing_key = self._sign_bytes(kService, "aws4_request")
|
|
370
|
+
|
|
371
|
+
signature = self._sign_bytes_hex(signing_key, string_to_sign).lower()
|
|
372
|
+
authorization_header = (
|
|
373
|
+
"AWS4-HMAC-SHA256 "
|
|
374
|
+
+ f"Credential={self.credentials.creds['AWS_KEY_ID']}/{scope}, "
|
|
375
|
+
+ f"SignedHeaders={signed_headers}, "
|
|
376
|
+
+ f"Signature={signature.decode('utf-8')}"
|
|
377
|
+
)
|
|
378
|
+
headers.update(x_amz_headers)
|
|
379
|
+
headers["Authorization"] = authorization_header
|
|
380
|
+
rest_args = {"headers": headers}
|
|
381
|
+
|
|
382
|
+
if payload:
|
|
383
|
+
rest_args["data"] = payload
|
|
384
|
+
|
|
385
|
+
# add customized hook: to remove content-encoding from response.
|
|
386
|
+
if ignore_content_encoding:
|
|
387
|
+
rest_args["hooks"] = {"response": remove_content_encoding}
|
|
388
|
+
|
|
389
|
+
return url.encode("utf-8"), rest_args
|
|
390
|
+
|
|
391
|
+
return self._send_request_with_retry(
|
|
392
|
+
verb, generate_authenticated_url_and_args_v4, retry_id
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
def get_file_header(self, filename: str) -> FileHeader | None:
|
|
396
|
+
"""Gets the metadata of file in specified location.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
filename: Name of remote file.
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
None if HEAD returns 404, otherwise a FileHeader instance populated
|
|
403
|
+
with metadata
|
|
404
|
+
"""
|
|
405
|
+
path = quote(self.s3location.path + filename.lstrip("/"))
|
|
406
|
+
url = self.endpoint + f"/{path}"
|
|
407
|
+
|
|
408
|
+
retry_id = "HEAD"
|
|
409
|
+
self.retry_count[retry_id] = 0
|
|
410
|
+
response = self._send_request_with_authentication_and_retry(
|
|
411
|
+
url=url, verb="HEAD", retry_id=retry_id
|
|
412
|
+
)
|
|
413
|
+
if response.status_code == 200:
|
|
414
|
+
self.meta.result_status = ResultStatus.UPLOADED
|
|
415
|
+
metadata = response.headers
|
|
416
|
+
encryption_metadata = (
|
|
417
|
+
EncryptionMetadata(
|
|
418
|
+
key=metadata.get(META_PREFIX + AMZ_KEY),
|
|
419
|
+
iv=metadata.get(META_PREFIX + AMZ_IV),
|
|
420
|
+
matdesc=metadata.get(META_PREFIX + AMZ_MATDESC),
|
|
421
|
+
)
|
|
422
|
+
if metadata.get(META_PREFIX + AMZ_KEY)
|
|
423
|
+
else None
|
|
424
|
+
)
|
|
425
|
+
return FileHeader(
|
|
426
|
+
digest=metadata.get(META_PREFIX + SFC_DIGEST),
|
|
427
|
+
content_length=int(metadata.get("Content-Length")),
|
|
428
|
+
encryption_metadata=encryption_metadata,
|
|
429
|
+
)
|
|
430
|
+
elif response.status_code == 404:
|
|
431
|
+
logger.debug(
|
|
432
|
+
f"not found. bucket: {self.s3location.bucket_name}, path: {path}"
|
|
433
|
+
)
|
|
434
|
+
self.meta.result_status = ResultStatus.NOT_FOUND_FILE
|
|
435
|
+
return None
|
|
436
|
+
else:
|
|
437
|
+
response.raise_for_status()
|
|
438
|
+
|
|
439
|
+
def _prepare_file_metadata(self) -> dict[str, Any]:
|
|
440
|
+
"""Construct metadata for a file to be uploaded.
|
|
441
|
+
|
|
442
|
+
Returns: File metadata in a dict.
|
|
443
|
+
|
|
444
|
+
"""
|
|
445
|
+
s3_metadata = {
|
|
446
|
+
META_PREFIX + SFC_DIGEST: self.meta.sha256_digest,
|
|
447
|
+
}
|
|
448
|
+
if self.encryption_metadata:
|
|
449
|
+
s3_metadata.update(
|
|
450
|
+
{
|
|
451
|
+
META_PREFIX + AMZ_IV: self.encryption_metadata.iv,
|
|
452
|
+
META_PREFIX + AMZ_KEY: self.encryption_metadata.key,
|
|
453
|
+
META_PREFIX + AMZ_MATDESC: self.encryption_metadata.matdesc,
|
|
454
|
+
}
|
|
455
|
+
)
|
|
456
|
+
return s3_metadata
|
|
457
|
+
|
|
458
|
+
def _initiate_multipart_upload(self) -> None:
|
|
459
|
+
query_parts = (("uploads", ""),)
|
|
460
|
+
path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
|
|
461
|
+
query_string = self._construct_query_string(query_parts)
|
|
462
|
+
url = self.endpoint + f"/{path}?{query_string}"
|
|
463
|
+
s3_metadata = self._prepare_file_metadata()
|
|
464
|
+
# initiate multipart upload
|
|
465
|
+
retry_id = "Initiate"
|
|
466
|
+
self.retry_count[retry_id] = 0
|
|
467
|
+
response = self._send_request_with_authentication_and_retry(
|
|
468
|
+
url=url,
|
|
469
|
+
verb="POST",
|
|
470
|
+
retry_id=retry_id,
|
|
471
|
+
x_amz_headers=s3_metadata,
|
|
472
|
+
headers={HTTP_HEADER_CONTENT_TYPE: HTTP_HEADER_VALUE_OCTET_STREAM},
|
|
473
|
+
query_parts=dict(query_parts),
|
|
474
|
+
)
|
|
475
|
+
if response.status_code == 200:
|
|
476
|
+
self.upload_id = ET.fromstring(response.content)[2].text
|
|
477
|
+
self.etags = [None] * self.num_of_chunks
|
|
478
|
+
else:
|
|
479
|
+
response.raise_for_status()
|
|
480
|
+
|
|
481
|
+
def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
|
|
482
|
+
path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
|
|
483
|
+
url = self.endpoint + f"/{path}"
|
|
484
|
+
|
|
485
|
+
if self.num_of_chunks == 1: # single request
|
|
486
|
+
s3_metadata = self._prepare_file_metadata()
|
|
487
|
+
response = self._send_request_with_authentication_and_retry(
|
|
488
|
+
url=url,
|
|
489
|
+
verb="PUT",
|
|
490
|
+
retry_id=chunk_id,
|
|
491
|
+
payload=chunk,
|
|
492
|
+
x_amz_headers=s3_metadata,
|
|
493
|
+
headers={HTTP_HEADER_CONTENT_TYPE: HTTP_HEADER_VALUE_OCTET_STREAM},
|
|
494
|
+
unsigned_payload=True,
|
|
495
|
+
)
|
|
496
|
+
response.raise_for_status()
|
|
497
|
+
else:
|
|
498
|
+
# multipart PUT
|
|
499
|
+
query_parts = (
|
|
500
|
+
("partNumber", str(chunk_id + 1)),
|
|
501
|
+
("uploadId", self.upload_id),
|
|
502
|
+
)
|
|
503
|
+
query_string = self._construct_query_string(query_parts)
|
|
504
|
+
chunk_url = f"{url}?{query_string}"
|
|
505
|
+
response = self._send_request_with_authentication_and_retry(
|
|
506
|
+
url=chunk_url,
|
|
507
|
+
verb="PUT",
|
|
508
|
+
retry_id=chunk_id,
|
|
509
|
+
payload=chunk,
|
|
510
|
+
unsigned_payload=True,
|
|
511
|
+
query_parts=dict(query_parts),
|
|
512
|
+
)
|
|
513
|
+
if response.status_code == 200:
|
|
514
|
+
self.etags[chunk_id] = response.headers["ETag"]
|
|
515
|
+
response.raise_for_status()
|
|
516
|
+
|
|
517
|
+
def _complete_multipart_upload(self) -> None:
|
|
518
|
+
query_parts = (("uploadId", self.upload_id),)
|
|
519
|
+
path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
|
|
520
|
+
query_string = self._construct_query_string(query_parts)
|
|
521
|
+
url = self.endpoint + f"/{path}?{query_string}"
|
|
522
|
+
logger.debug("Initiating multipart upload complete")
|
|
523
|
+
# Complete multipart upload
|
|
524
|
+
root = ET.Element("CompleteMultipartUpload")
|
|
525
|
+
for idx, etag_str in enumerate(self.etags):
|
|
526
|
+
part = ET.Element("Part")
|
|
527
|
+
etag = ET.Element("ETag")
|
|
528
|
+
etag.text = etag_str
|
|
529
|
+
part.append(etag)
|
|
530
|
+
part_number = ET.Element("PartNumber")
|
|
531
|
+
part_number.text = str(idx + 1)
|
|
532
|
+
part.append(part_number)
|
|
533
|
+
root.append(part)
|
|
534
|
+
retry_id = "Complete"
|
|
535
|
+
self.retry_count[retry_id] = 0
|
|
536
|
+
response = self._send_request_with_authentication_and_retry(
|
|
537
|
+
url=url,
|
|
538
|
+
verb="POST",
|
|
539
|
+
retry_id=retry_id,
|
|
540
|
+
payload=ET.tostring(root),
|
|
541
|
+
query_parts=dict(query_parts),
|
|
542
|
+
)
|
|
543
|
+
response.raise_for_status()
|
|
544
|
+
|
|
545
|
+
def _abort_multipart_upload(self) -> None:
|
|
546
|
+
if self.upload_id is None:
|
|
547
|
+
return
|
|
548
|
+
query_parts = (("uploadId", self.upload_id),)
|
|
549
|
+
path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
|
|
550
|
+
query_string = self._construct_query_string(query_parts)
|
|
551
|
+
url = self.endpoint + f"/{path}?{query_string}"
|
|
552
|
+
|
|
553
|
+
retry_id = "Abort"
|
|
554
|
+
self.retry_count[retry_id] = 0
|
|
555
|
+
response = self._send_request_with_authentication_and_retry(
|
|
556
|
+
url=url,
|
|
557
|
+
verb="DELETE",
|
|
558
|
+
retry_id=retry_id,
|
|
559
|
+
query_parts=dict(query_parts),
|
|
560
|
+
)
|
|
561
|
+
response.raise_for_status()
|
|
562
|
+
|
|
563
|
+
def download_chunk(self, chunk_id: int) -> None:
|
|
564
|
+
logger.debug(f"Downloading chunk {chunk_id}")
|
|
565
|
+
path = quote(self.s3location.path + self.meta.src_file_name.lstrip("/"))
|
|
566
|
+
url = self.endpoint + f"/{path}"
|
|
567
|
+
if self.num_of_chunks == 1:
|
|
568
|
+
response = self._send_request_with_authentication_and_retry(
|
|
569
|
+
url=url,
|
|
570
|
+
verb="GET",
|
|
571
|
+
retry_id=chunk_id,
|
|
572
|
+
ignore_content_encoding=True,
|
|
573
|
+
)
|
|
574
|
+
if response.status_code == 200:
|
|
575
|
+
self.write_downloaded_chunk(0, response.content)
|
|
576
|
+
self.meta.result_status = ResultStatus.DOWNLOADED
|
|
577
|
+
response.raise_for_status()
|
|
578
|
+
else:
|
|
579
|
+
chunk_size = self.chunk_size
|
|
580
|
+
if chunk_id < self.num_of_chunks - 1:
|
|
581
|
+
_range = f"{chunk_id * chunk_size}-{(chunk_id+1)*chunk_size-1}"
|
|
582
|
+
else:
|
|
583
|
+
_range = f"{chunk_id * chunk_size}-"
|
|
584
|
+
|
|
585
|
+
response = self._send_request_with_authentication_and_retry(
|
|
586
|
+
url=url,
|
|
587
|
+
verb="GET",
|
|
588
|
+
retry_id=chunk_id,
|
|
589
|
+
headers={"Range": f"bytes={_range}"},
|
|
590
|
+
)
|
|
591
|
+
if response.status_code in (200, 206):
|
|
592
|
+
self.write_downloaded_chunk(chunk_id, response.content)
|
|
593
|
+
response.raise_for_status()
|
|
594
|
+
|
|
595
|
+
def _get_bucket_accelerate_config(self, bucket_name: str) -> bool:
|
|
596
|
+
query_parts = (("accelerate", ""),)
|
|
597
|
+
query_string = self._construct_query_string(query_parts)
|
|
598
|
+
url = f"https://{bucket_name}.s3.amazonaws.com/?{query_string}"
|
|
599
|
+
retry_id = "accelerate"
|
|
600
|
+
self.retry_count[retry_id] = 0
|
|
601
|
+
response = self._send_request_with_authentication_and_retry(
|
|
602
|
+
url=url, verb="GET", retry_id=retry_id, query_parts=dict(query_parts)
|
|
603
|
+
)
|
|
604
|
+
if response.status_code == 200:
|
|
605
|
+
config = ET.fromstring(response.text)
|
|
606
|
+
namespace = config.tag[: config.tag.index("}") + 1]
|
|
607
|
+
statusTag = f"{namespace}Status"
|
|
608
|
+
found = config.find(statusTag)
|
|
609
|
+
use_accelerate_endpoint = (
|
|
610
|
+
False if found is None else (found.text == "Enabled")
|
|
611
|
+
)
|
|
612
|
+
logger.debug(f"use_accelerate_endpoint: {use_accelerate_endpoint}")
|
|
613
|
+
return use_accelerate_endpoint
|
|
614
|
+
return False
|