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,153 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import gzip
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import struct
|
|
8
|
+
from io import BytesIO
|
|
9
|
+
from logging import getLogger
|
|
10
|
+
from typing import IO
|
|
11
|
+
|
|
12
|
+
from cryptography.hazmat.backends import default_backend
|
|
13
|
+
from cryptography.hazmat.primitives import hashes
|
|
14
|
+
|
|
15
|
+
from .constants import UTF8, kilobyte
|
|
16
|
+
|
|
17
|
+
logger = getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def owner_rw_opener(path, flags) -> int:
|
|
21
|
+
return os.open(path, flags, mode=0o600)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SnowflakeFileUtil:
|
|
25
|
+
@staticmethod
|
|
26
|
+
def get_digest_and_size(src: IO[bytes]) -> tuple[str, int]:
|
|
27
|
+
"""Gets stream digest and size.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
src: The input stream.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Tuple of src's digest and src's size in bytes.
|
|
34
|
+
"""
|
|
35
|
+
CHUNK_SIZE = 64 * kilobyte
|
|
36
|
+
backend = default_backend()
|
|
37
|
+
chosen_hash = hashes.SHA256()
|
|
38
|
+
hasher = hashes.Hash(chosen_hash, backend)
|
|
39
|
+
while True:
|
|
40
|
+
chunk = src.read(CHUNK_SIZE)
|
|
41
|
+
if chunk == b"":
|
|
42
|
+
break
|
|
43
|
+
hasher.update(chunk)
|
|
44
|
+
|
|
45
|
+
digest = base64.standard_b64encode(hasher.finalize()).decode(UTF8)
|
|
46
|
+
|
|
47
|
+
size = src.tell()
|
|
48
|
+
src.seek(0)
|
|
49
|
+
return digest, size
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def compress_with_gzip_from_stream(src_stream: IO[bytes]) -> tuple[IO[bytes], int]:
|
|
53
|
+
"""Compresses a stream of bytes with GZIP.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
src_stream: bytes stream
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
A tuple of byte stream and size.
|
|
60
|
+
"""
|
|
61
|
+
compressed_data = gzip.compress(src_stream.read())
|
|
62
|
+
src_stream.seek(0)
|
|
63
|
+
return BytesIO(compressed_data), len(compressed_data)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def compress_file_with_gzip(file_name: str, tmp_dir: str) -> tuple[str, int]:
|
|
67
|
+
"""Compresses a file with GZIP.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
file_name: Local path to file to be compressed.
|
|
71
|
+
tmp_dir: Temporary directory where an GZIP file will be created.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
A tuple of gzip file name and size.
|
|
75
|
+
"""
|
|
76
|
+
base_name = os.path.basename(file_name)
|
|
77
|
+
gzip_file_name = os.path.join(tmp_dir, base_name + "_c.gz")
|
|
78
|
+
logger.debug("gzip file: %s, original file: %s", gzip_file_name, file_name)
|
|
79
|
+
with open(file_name, "rb") as fr:
|
|
80
|
+
with gzip.GzipFile(gzip_file_name, "wb") as fw:
|
|
81
|
+
shutil.copyfileobj(fr, fw, length=64 * kilobyte)
|
|
82
|
+
SnowflakeFileUtil.normalize_gzip_header(gzip_file_name)
|
|
83
|
+
|
|
84
|
+
statinfo = os.stat(gzip_file_name)
|
|
85
|
+
return gzip_file_name, statinfo.st_size
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def normalize_gzip_header(gzip_file_name: str) -> None:
|
|
89
|
+
"""Normalizes GZIP file header.
|
|
90
|
+
|
|
91
|
+
For consistent file digest, this removes creation timestamp and file name from the header.
|
|
92
|
+
For more information see http://www.zlib.org/rfc-gzip.html#file-format
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
gzip_file_name: Local path of gzip file.
|
|
96
|
+
"""
|
|
97
|
+
with open(gzip_file_name, "r+b") as f:
|
|
98
|
+
# reset the timestamp in gzip header
|
|
99
|
+
f.seek(3, 0)
|
|
100
|
+
# Read flags bit
|
|
101
|
+
flag_byte = f.read(1)
|
|
102
|
+
flags = struct.unpack("B", flag_byte)[0]
|
|
103
|
+
f.seek(4, 0)
|
|
104
|
+
f.write(struct.pack("<L", 0))
|
|
105
|
+
# Reset the file name in gzip header if included
|
|
106
|
+
if flags & 8:
|
|
107
|
+
f.seek(10, 0)
|
|
108
|
+
# Skip through xlen bytes and length if included
|
|
109
|
+
if flags & 4:
|
|
110
|
+
xlen_bytes = f.read(2)
|
|
111
|
+
xlen = struct.unpack("<H", xlen_bytes)[0]
|
|
112
|
+
f.seek(10 + 2 + xlen)
|
|
113
|
+
byte = f.read(1)
|
|
114
|
+
while byte:
|
|
115
|
+
value = struct.unpack("B", byte)[0]
|
|
116
|
+
# logger.debug('ch=%s, byte=%s', value, byte)
|
|
117
|
+
if value == 0:
|
|
118
|
+
break
|
|
119
|
+
f.seek(-1, 1) # current_pos - 1
|
|
120
|
+
f.write(struct.pack("B", 0x20)) # replace with a space
|
|
121
|
+
byte = f.read(1)
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def get_digest_and_size_for_stream(src_stream: IO[bytes]) -> tuple[str, int]:
|
|
125
|
+
"""Gets stream digest and size.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
src_stream: The input source stream.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Tuple of src_stream's digest and src_stream's size in bytes.
|
|
132
|
+
"""
|
|
133
|
+
digest, size = SnowflakeFileUtil.get_digest_and_size(src_stream)
|
|
134
|
+
logger.debug("getting digest and size for stream: %s, %s", digest, size)
|
|
135
|
+
return digest, size
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def get_digest_and_size_for_file(file_name: str) -> tuple[str, int]:
|
|
139
|
+
"""Gets file digest and size.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
file_name: Local path to a file.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Tuple of file's digest and file size in bytes.
|
|
146
|
+
"""
|
|
147
|
+
digest, size = None, None
|
|
148
|
+
with open(file_name, "rb") as src:
|
|
149
|
+
digest, size = SnowflakeFileUtil.get_digest_and_size(src)
|
|
150
|
+
logger.debug(
|
|
151
|
+
"getting digest and size: %s, %s, file=%s", digest, size, file_name
|
|
152
|
+
)
|
|
153
|
+
return digest, size
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from logging import getLogger
|
|
7
|
+
from typing import TYPE_CHECKING, Any, NamedTuple
|
|
8
|
+
|
|
9
|
+
from ._sql_util import is_put_statement
|
|
10
|
+
from .compat import quote
|
|
11
|
+
from .constants import (
|
|
12
|
+
FILE_PROTOCOL,
|
|
13
|
+
HTTP_HEADER_CONTENT_ENCODING,
|
|
14
|
+
FileHeader,
|
|
15
|
+
ResultStatus,
|
|
16
|
+
kilobyte,
|
|
17
|
+
)
|
|
18
|
+
from .encryption_util import EncryptionMetadata
|
|
19
|
+
from .storage_client import SnowflakeStorageClient
|
|
20
|
+
from .vendored import requests
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
23
|
+
from .connection import SnowflakeConnection
|
|
24
|
+
from .file_transfer_agent import SnowflakeFileMeta, StorageCredential
|
|
25
|
+
|
|
26
|
+
logger = getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
GCS_METADATA_PREFIX = "x-goog-meta-"
|
|
29
|
+
GCS_METADATA_SFC_DIGEST = GCS_METADATA_PREFIX + "sfc-digest"
|
|
30
|
+
GCS_METADATA_MATDESC_KEY = GCS_METADATA_PREFIX + "matdesc"
|
|
31
|
+
GCS_METADATA_ENCRYPTIONDATAPROP = GCS_METADATA_PREFIX + "encryptiondata"
|
|
32
|
+
GCS_FILE_HEADER_DIGEST = "gcs-file-header-digest"
|
|
33
|
+
GCS_FILE_HEADER_CONTENT_LENGTH = "gcs-file-header-content-length"
|
|
34
|
+
GCS_FILE_HEADER_ENCRYPTION_METADATA = "gcs-file-header-encryption-metadata"
|
|
35
|
+
GCS_REGION_ME_CENTRAL_2 = "me-central2"
|
|
36
|
+
CONTENT_CHUNK_SIZE = 10 * kilobyte
|
|
37
|
+
ACCESS_TOKEN = "GCS_ACCESS_TOKEN"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class GcsLocation(NamedTuple):
|
|
41
|
+
bucket_name: str
|
|
42
|
+
path: str
|
|
43
|
+
endpoint: str = "https://storage.googleapis.com"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SnowflakeGCSRestClient(SnowflakeStorageClient):
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
meta: SnowflakeFileMeta,
|
|
50
|
+
credentials: StorageCredential,
|
|
51
|
+
stage_info: dict[str, Any],
|
|
52
|
+
cnx: SnowflakeConnection,
|
|
53
|
+
command: str,
|
|
54
|
+
unsafe_file_write: bool = False,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Creates a client object with given stage credentials.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
stage_info: Access credentials and info of a stage.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The client to communicate with GCS.
|
|
63
|
+
"""
|
|
64
|
+
super().__init__(
|
|
65
|
+
meta,
|
|
66
|
+
stage_info,
|
|
67
|
+
-1,
|
|
68
|
+
credentials=credentials,
|
|
69
|
+
chunked_transfer=False,
|
|
70
|
+
unsafe_file_write=unsafe_file_write,
|
|
71
|
+
)
|
|
72
|
+
self.stage_info = stage_info
|
|
73
|
+
self._command = command
|
|
74
|
+
self.meta = meta
|
|
75
|
+
self._cursor = cnx.cursor()
|
|
76
|
+
# presigned_url in meta is for downloading
|
|
77
|
+
self.presigned_url: str = meta.presigned_url or stage_info.get("presignedUrl")
|
|
78
|
+
self.security_token = credentials.creds.get("GCS_ACCESS_TOKEN")
|
|
79
|
+
self.use_regional_url = (
|
|
80
|
+
"region" in stage_info
|
|
81
|
+
and stage_info["region"].lower() == GCS_REGION_ME_CENTRAL_2
|
|
82
|
+
or "useRegionalUrl" in stage_info
|
|
83
|
+
and stage_info["useRegionalUrl"]
|
|
84
|
+
)
|
|
85
|
+
self.endpoint: str | None = (
|
|
86
|
+
None if "endPoint" not in stage_info else stage_info["endPoint"]
|
|
87
|
+
)
|
|
88
|
+
self.use_virtual_url: bool = (
|
|
89
|
+
"useVirtualUrl" in stage_info and stage_info["useVirtualUrl"]
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if self.security_token:
|
|
93
|
+
logger.debug(f"len(GCS_ACCESS_TOKEN): {len(self.security_token)}")
|
|
94
|
+
else:
|
|
95
|
+
logger.debug("No access token received from GS, requesting presigned url")
|
|
96
|
+
self._update_presigned_url()
|
|
97
|
+
|
|
98
|
+
def _has_expired_token(self, response: requests.Response) -> bool:
|
|
99
|
+
return self.security_token and response.status_code == 401
|
|
100
|
+
|
|
101
|
+
def _has_expired_presigned_url(self, response: requests.Response) -> bool:
|
|
102
|
+
# Presigned urls can be generated for any xml-api operation
|
|
103
|
+
# offered by GCS. Hence, the error codes expected are similar
|
|
104
|
+
# to xml api.
|
|
105
|
+
# https://cloud.google.com/storage/docs/xml-api/reference-status
|
|
106
|
+
|
|
107
|
+
presigned_url_expired = (
|
|
108
|
+
not self.security_token
|
|
109
|
+
) and response.status_code == 400
|
|
110
|
+
if presigned_url_expired and self.last_err_is_presigned_url:
|
|
111
|
+
logger.debug("Presigned url expiration error two times in a row.")
|
|
112
|
+
response.raise_for_status()
|
|
113
|
+
self.last_err_is_presigned_url = presigned_url_expired
|
|
114
|
+
return presigned_url_expired
|
|
115
|
+
|
|
116
|
+
def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
|
|
117
|
+
meta = self.meta
|
|
118
|
+
|
|
119
|
+
content_encoding = ""
|
|
120
|
+
if meta.dst_compression_type is not None:
|
|
121
|
+
content_encoding = meta.dst_compression_type.name.lower()
|
|
122
|
+
|
|
123
|
+
# We set the contentEncoding to blank for GZIP files. We don't
|
|
124
|
+
# want GCS to think our gzip files are gzips because it makes
|
|
125
|
+
# them download uncompressed, and none of the other providers do
|
|
126
|
+
# that. There's essentially no way for us to prevent that
|
|
127
|
+
# behavior. Bad Google.
|
|
128
|
+
if content_encoding and content_encoding == "gzip":
|
|
129
|
+
content_encoding = ""
|
|
130
|
+
|
|
131
|
+
gcs_headers = {
|
|
132
|
+
HTTP_HEADER_CONTENT_ENCODING: content_encoding,
|
|
133
|
+
GCS_METADATA_SFC_DIGEST: meta.sha256_digest,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if self.encryption_metadata:
|
|
137
|
+
gcs_headers.update(
|
|
138
|
+
{
|
|
139
|
+
GCS_METADATA_ENCRYPTIONDATAPROP: json.dumps(
|
|
140
|
+
{
|
|
141
|
+
"EncryptionMode": "FullBlob",
|
|
142
|
+
"WrappedContentKey": {
|
|
143
|
+
"KeyId": "symmKey1",
|
|
144
|
+
"EncryptedKey": self.encryption_metadata.key,
|
|
145
|
+
"Algorithm": "AES_CBC_256",
|
|
146
|
+
},
|
|
147
|
+
"EncryptionAgent": {
|
|
148
|
+
"Protocol": "1.0",
|
|
149
|
+
"EncryptionAlgorithm": "AES_CBC_256",
|
|
150
|
+
},
|
|
151
|
+
"ContentEncryptionIV": self.encryption_metadata.iv,
|
|
152
|
+
"KeyWrappingMetadata": {"EncryptionLibrary": "Java 5.3.0"},
|
|
153
|
+
}
|
|
154
|
+
),
|
|
155
|
+
GCS_METADATA_MATDESC_KEY: self.encryption_metadata.matdesc,
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def generate_url_and_rest_args() -> (
|
|
160
|
+
tuple[str, dict[str, dict[str | Any, str | None] | bytes]]
|
|
161
|
+
):
|
|
162
|
+
if not self.presigned_url:
|
|
163
|
+
upload_url = self.generate_file_url(
|
|
164
|
+
self.stage_info["location"],
|
|
165
|
+
meta.dst_file_name.lstrip("/"),
|
|
166
|
+
self.use_regional_url,
|
|
167
|
+
(
|
|
168
|
+
None
|
|
169
|
+
if "region" not in self.stage_info
|
|
170
|
+
else self.stage_info["region"]
|
|
171
|
+
),
|
|
172
|
+
self.endpoint,
|
|
173
|
+
self.use_virtual_url,
|
|
174
|
+
)
|
|
175
|
+
access_token = self.security_token
|
|
176
|
+
else:
|
|
177
|
+
upload_url = self.presigned_url
|
|
178
|
+
access_token: str | None = None
|
|
179
|
+
if access_token:
|
|
180
|
+
gcs_headers.update({"Authorization": f"Bearer {access_token}"})
|
|
181
|
+
rest_args = {"headers": gcs_headers, "data": chunk}
|
|
182
|
+
return upload_url, rest_args
|
|
183
|
+
|
|
184
|
+
response = self._send_request_with_retry(
|
|
185
|
+
"PUT", generate_url_and_rest_args, chunk_id
|
|
186
|
+
)
|
|
187
|
+
response.raise_for_status()
|
|
188
|
+
meta.gcs_file_header_digest = gcs_headers[GCS_METADATA_SFC_DIGEST]
|
|
189
|
+
meta.gcs_file_header_content_length = meta.upload_size
|
|
190
|
+
meta.gcs_file_header_encryption_metadata = json.loads(
|
|
191
|
+
gcs_headers.get(GCS_METADATA_ENCRYPTIONDATAPROP, "null")
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def download_chunk(self, chunk_id: int) -> None:
|
|
195
|
+
meta = self.meta
|
|
196
|
+
|
|
197
|
+
def generate_url_and_rest_args() -> (
|
|
198
|
+
tuple[str, dict[str, dict[str, str] | bool]]
|
|
199
|
+
):
|
|
200
|
+
gcs_headers = {}
|
|
201
|
+
if not self.presigned_url:
|
|
202
|
+
download_url = self.generate_file_url(
|
|
203
|
+
self.stage_info["location"],
|
|
204
|
+
meta.src_file_name.lstrip("/"),
|
|
205
|
+
self.use_regional_url,
|
|
206
|
+
(
|
|
207
|
+
None
|
|
208
|
+
if "region" not in self.stage_info
|
|
209
|
+
else self.stage_info["region"]
|
|
210
|
+
),
|
|
211
|
+
self.endpoint,
|
|
212
|
+
self.use_virtual_url,
|
|
213
|
+
)
|
|
214
|
+
access_token = self.security_token
|
|
215
|
+
gcs_headers["Authorization"] = f"Bearer {access_token}"
|
|
216
|
+
else:
|
|
217
|
+
download_url = self.presigned_url
|
|
218
|
+
rest_args = {"headers": gcs_headers, "stream": True}
|
|
219
|
+
return download_url, rest_args
|
|
220
|
+
|
|
221
|
+
response = self._send_request_with_retry(
|
|
222
|
+
"GET", generate_url_and_rest_args, chunk_id
|
|
223
|
+
)
|
|
224
|
+
response.raise_for_status()
|
|
225
|
+
|
|
226
|
+
self.write_downloaded_chunk(chunk_id, response.content)
|
|
227
|
+
|
|
228
|
+
encryption_metadata = None
|
|
229
|
+
|
|
230
|
+
if response.headers.get(GCS_METADATA_ENCRYPTIONDATAPROP, None):
|
|
231
|
+
encryptiondata = json.loads(
|
|
232
|
+
response.headers[GCS_METADATA_ENCRYPTIONDATAPROP]
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if encryptiondata:
|
|
236
|
+
encryption_metadata = EncryptionMetadata(
|
|
237
|
+
key=encryptiondata["WrappedContentKey"]["EncryptedKey"],
|
|
238
|
+
iv=encryptiondata["ContentEncryptionIV"],
|
|
239
|
+
matdesc=(
|
|
240
|
+
response.headers[GCS_METADATA_MATDESC_KEY]
|
|
241
|
+
if GCS_METADATA_MATDESC_KEY in response.headers
|
|
242
|
+
else None
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
meta.gcs_file_header_digest = response.headers.get(GCS_METADATA_SFC_DIGEST)
|
|
247
|
+
meta.gcs_file_header_content_length = len(response.content)
|
|
248
|
+
meta.gcs_file_header_encryption_metadata = encryption_metadata
|
|
249
|
+
|
|
250
|
+
def finish_download(self) -> None:
|
|
251
|
+
super().finish_download()
|
|
252
|
+
# Sadly, we can only determine the src file size after we've
|
|
253
|
+
# downloaded it, unlike the other cloud providers where the
|
|
254
|
+
# metadata can be read beforehand.
|
|
255
|
+
self.meta.src_file_size = os.path.getsize(self.full_dst_file_name)
|
|
256
|
+
|
|
257
|
+
def _update_presigned_url(self) -> None:
|
|
258
|
+
"""Updates the file metas with presigned urls if any.
|
|
259
|
+
|
|
260
|
+
Currently only the file metas generated for PUT/GET on a GCP account need the presigned urls.
|
|
261
|
+
"""
|
|
262
|
+
logger.debug("Updating presigned url")
|
|
263
|
+
|
|
264
|
+
# Rewrite the command such that a new PUT call is made for each file
|
|
265
|
+
# represented by the regex (if present) separately. This is the only
|
|
266
|
+
# way to get the presigned url for that file.
|
|
267
|
+
file_path_to_be_replaced = self._get_local_file_path_from_put_command()
|
|
268
|
+
|
|
269
|
+
if not file_path_to_be_replaced:
|
|
270
|
+
# This prevents GET statements to proceed
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
# At this point the connector has already figured out and
|
|
274
|
+
# validated that the local file exists and has also decided
|
|
275
|
+
# upon the destination file name and the compression type.
|
|
276
|
+
# The only thing that's left to do is to get the presigned
|
|
277
|
+
# url for the destination file. If the command originally
|
|
278
|
+
# referred to a single file, then the presigned url got in
|
|
279
|
+
# that case is simply ignore, since the file name is not what
|
|
280
|
+
# we want.
|
|
281
|
+
|
|
282
|
+
# GS only looks at the file name at the end of local file
|
|
283
|
+
# path to figure out the remote object name. Hence the prefix
|
|
284
|
+
# for local path is not necessary in the reconstructed command.
|
|
285
|
+
file_path_to_replace_with = self.meta.dst_file_name
|
|
286
|
+
command_with_single_file = self._command
|
|
287
|
+
command_with_single_file = command_with_single_file.replace(
|
|
288
|
+
file_path_to_be_replaced, file_path_to_replace_with
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
logger.debug("getting presigned url for %s", file_path_to_replace_with)
|
|
292
|
+
ret = self._cursor._execute_helper(command_with_single_file)
|
|
293
|
+
|
|
294
|
+
stage_info = ret.get("data", dict()).get("stageInfo", dict())
|
|
295
|
+
self.meta.presigned_url = stage_info.get("presignedUrl")
|
|
296
|
+
self.presigned_url = stage_info.get("presignedUrl")
|
|
297
|
+
|
|
298
|
+
def _get_local_file_path_from_put_command(self) -> str | None:
|
|
299
|
+
"""Get the local file path from PUT command (Logic adopted from JDBC, written by Polita).
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
command: Command to be parsed and get the local file path out of.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
The local file path.
|
|
306
|
+
"""
|
|
307
|
+
command = self._command
|
|
308
|
+
if FILE_PROTOCOL not in self._command or not is_put_statement(command):
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
file_path_begin_index = command.find(FILE_PROTOCOL)
|
|
312
|
+
is_file_path_quoted = command[file_path_begin_index - 1] == "'"
|
|
313
|
+
file_path_begin_index += len(FILE_PROTOCOL)
|
|
314
|
+
|
|
315
|
+
file_path = ""
|
|
316
|
+
|
|
317
|
+
if is_file_path_quoted:
|
|
318
|
+
file_path_end_index = command.find("'", file_path_begin_index)
|
|
319
|
+
|
|
320
|
+
if file_path_end_index > file_path_begin_index:
|
|
321
|
+
file_path = command[file_path_begin_index:file_path_end_index]
|
|
322
|
+
else:
|
|
323
|
+
index_list = []
|
|
324
|
+
for delimiter in [" ", "\n", ";"]:
|
|
325
|
+
index = command.find(delimiter, file_path_begin_index)
|
|
326
|
+
if index != -1:
|
|
327
|
+
index_list += [index]
|
|
328
|
+
|
|
329
|
+
file_path_end_index = min(index_list) if index_list else -1
|
|
330
|
+
|
|
331
|
+
if file_path_end_index > file_path_begin_index:
|
|
332
|
+
file_path = command[file_path_begin_index:file_path_end_index]
|
|
333
|
+
elif file_path_end_index == -1:
|
|
334
|
+
file_path = command[file_path_begin_index:]
|
|
335
|
+
|
|
336
|
+
return file_path
|
|
337
|
+
|
|
338
|
+
def get_file_header(self, filename: str) -> FileHeader | None:
|
|
339
|
+
"""Gets the remote file's metadata.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
filename: Not applicable to GCS.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
The file header, with expected properties populated or None, based on how the request goes with the
|
|
346
|
+
storage provider.
|
|
347
|
+
|
|
348
|
+
Notes:
|
|
349
|
+
Sometimes this method is called to verify that the file has indeed been uploaded. In cases of presigned
|
|
350
|
+
url, we have no way of verifying that, except with the http status code of 200 which we have already
|
|
351
|
+
confirmed and set the meta.result_status = UPLOADED/DOWNLOADED.
|
|
352
|
+
"""
|
|
353
|
+
meta = self.meta
|
|
354
|
+
if (
|
|
355
|
+
meta.result_status == ResultStatus.UPLOADED
|
|
356
|
+
or meta.result_status == ResultStatus.DOWNLOADED
|
|
357
|
+
):
|
|
358
|
+
return FileHeader(
|
|
359
|
+
digest=meta.gcs_file_header_digest,
|
|
360
|
+
content_length=meta.gcs_file_header_content_length,
|
|
361
|
+
encryption_metadata=meta.gcs_file_header_encryption_metadata,
|
|
362
|
+
)
|
|
363
|
+
elif self.presigned_url:
|
|
364
|
+
meta.result_status = ResultStatus.NOT_FOUND_FILE
|
|
365
|
+
else:
|
|
366
|
+
|
|
367
|
+
def generate_url_and_authenticated_headers():
|
|
368
|
+
url = self.generate_file_url(
|
|
369
|
+
self.stage_info["location"],
|
|
370
|
+
filename.lstrip("/"),
|
|
371
|
+
self.use_regional_url,
|
|
372
|
+
(
|
|
373
|
+
None
|
|
374
|
+
if "region" not in self.stage_info
|
|
375
|
+
else self.stage_info["region"]
|
|
376
|
+
),
|
|
377
|
+
self.endpoint,
|
|
378
|
+
self.use_virtual_url,
|
|
379
|
+
)
|
|
380
|
+
gcs_headers = {"Authorization": f"Bearer {self.security_token}"}
|
|
381
|
+
rest_args = {"headers": gcs_headers}
|
|
382
|
+
return url, rest_args
|
|
383
|
+
|
|
384
|
+
retry_id = "HEAD"
|
|
385
|
+
self.retry_count[retry_id] = 0
|
|
386
|
+
response = self._send_request_with_retry(
|
|
387
|
+
"HEAD", generate_url_and_authenticated_headers, retry_id
|
|
388
|
+
)
|
|
389
|
+
if response.status_code == 404:
|
|
390
|
+
meta.result_status = ResultStatus.NOT_FOUND_FILE
|
|
391
|
+
return None
|
|
392
|
+
elif response.status_code == 200:
|
|
393
|
+
digest = response.headers.get(GCS_METADATA_SFC_DIGEST, None)
|
|
394
|
+
content_length = int(response.headers.get("content-length", "0"))
|
|
395
|
+
|
|
396
|
+
encryption_metadata = EncryptionMetadata("", "", "")
|
|
397
|
+
if response.headers.get(GCS_METADATA_ENCRYPTIONDATAPROP, None):
|
|
398
|
+
encryption_data = json.loads(
|
|
399
|
+
response.headers[GCS_METADATA_ENCRYPTIONDATAPROP]
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
if encryption_data:
|
|
403
|
+
encryption_metadata = EncryptionMetadata(
|
|
404
|
+
key=encryption_data["WrappedContentKey"]["EncryptedKey"],
|
|
405
|
+
iv=encryption_data["ContentEncryptionIV"],
|
|
406
|
+
matdesc=(
|
|
407
|
+
response.headers[GCS_METADATA_MATDESC_KEY]
|
|
408
|
+
if GCS_METADATA_MATDESC_KEY in response.headers
|
|
409
|
+
else None
|
|
410
|
+
),
|
|
411
|
+
)
|
|
412
|
+
meta.result_status = ResultStatus.UPLOADED
|
|
413
|
+
return FileHeader(
|
|
414
|
+
digest=digest,
|
|
415
|
+
content_length=content_length,
|
|
416
|
+
encryption_metadata=encryption_metadata,
|
|
417
|
+
)
|
|
418
|
+
response.raise_for_status()
|
|
419
|
+
return None
|
|
420
|
+
|
|
421
|
+
@staticmethod
|
|
422
|
+
def get_location(
|
|
423
|
+
stage_location: str,
|
|
424
|
+
use_regional_url: str = False,
|
|
425
|
+
region: str = None,
|
|
426
|
+
endpoint: str = None,
|
|
427
|
+
use_virtual_url: bool = False,
|
|
428
|
+
) -> GcsLocation:
|
|
429
|
+
container_name = stage_location
|
|
430
|
+
path = ""
|
|
431
|
+
|
|
432
|
+
# split stage location as bucket name and path
|
|
433
|
+
if "/" in stage_location:
|
|
434
|
+
container_name = stage_location[0 : stage_location.index("/")]
|
|
435
|
+
path = stage_location[stage_location.index("/") + 1 :]
|
|
436
|
+
if path and not path.endswith("/"):
|
|
437
|
+
path += "/"
|
|
438
|
+
if endpoint:
|
|
439
|
+
if endpoint.endswith("/"):
|
|
440
|
+
endpoint = endpoint[:-1]
|
|
441
|
+
return GcsLocation(bucket_name=container_name, path=path, endpoint=endpoint)
|
|
442
|
+
elif use_virtual_url:
|
|
443
|
+
return GcsLocation(
|
|
444
|
+
bucket_name=container_name,
|
|
445
|
+
path=path,
|
|
446
|
+
endpoint=f"https://{container_name}.storage.googleapis.com",
|
|
447
|
+
)
|
|
448
|
+
elif use_regional_url:
|
|
449
|
+
return GcsLocation(
|
|
450
|
+
bucket_name=container_name,
|
|
451
|
+
path=path,
|
|
452
|
+
endpoint=f"https://storage.{region.lower()}.rep.googleapis.com",
|
|
453
|
+
)
|
|
454
|
+
else:
|
|
455
|
+
return GcsLocation(bucket_name=container_name, path=path)
|
|
456
|
+
|
|
457
|
+
@staticmethod
|
|
458
|
+
def generate_file_url(
|
|
459
|
+
stage_location: str,
|
|
460
|
+
filename: str,
|
|
461
|
+
use_regional_url: str = False,
|
|
462
|
+
region: str = None,
|
|
463
|
+
endpoint: str = None,
|
|
464
|
+
use_virtual_url: bool = False,
|
|
465
|
+
) -> str:
|
|
466
|
+
gcs_location = SnowflakeGCSRestClient.get_location(
|
|
467
|
+
stage_location, use_regional_url, region, endpoint, use_virtual_url
|
|
468
|
+
)
|
|
469
|
+
full_file_path = f"{gcs_location.path}{filename}"
|
|
470
|
+
|
|
471
|
+
if use_virtual_url:
|
|
472
|
+
return f"{gcs_location.endpoint}/{quote(full_file_path)}"
|
|
473
|
+
else:
|
|
474
|
+
return f"{gcs_location.endpoint}/{gcs_location.bucket_name}/{quote(full_file_path)}"
|