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.
Files changed (246) hide show
  1. snowflake/connector/__init__.py +96 -0
  2. snowflake/connector/_query_context_cache.py +276 -0
  3. snowflake/connector/_sql_util.py +48 -0
  4. snowflake/connector/_utils.py +70 -0
  5. snowflake/connector/arrow_context.py +203 -0
  6. snowflake/connector/auth/__init__.py +53 -0
  7. snowflake/connector/auth/_auth.py +586 -0
  8. snowflake/connector/auth/_http_server.py +220 -0
  9. snowflake/connector/auth/_oauth_base.py +400 -0
  10. snowflake/connector/auth/by_plugin.py +219 -0
  11. snowflake/connector/auth/default.py +36 -0
  12. snowflake/connector/auth/idtoken.py +72 -0
  13. snowflake/connector/auth/keypair.py +222 -0
  14. snowflake/connector/auth/no_auth.py +39 -0
  15. snowflake/connector/auth/oauth.py +49 -0
  16. snowflake/connector/auth/oauth_code.py +479 -0
  17. snowflake/connector/auth/oauth_credentials.py +66 -0
  18. snowflake/connector/auth/okta.py +335 -0
  19. snowflake/connector/auth/pat.py +39 -0
  20. snowflake/connector/auth/usrpwdmfa.py +69 -0
  21. snowflake/connector/auth/webbrowser.py +502 -0
  22. snowflake/connector/auth/workload_identity.py +94 -0
  23. snowflake/connector/azure_storage_client.py +276 -0
  24. snowflake/connector/backoff_policies.py +141 -0
  25. snowflake/connector/bind_upload_agent.py +89 -0
  26. snowflake/connector/cache.py +696 -0
  27. snowflake/connector/compat.py +127 -0
  28. snowflake/connector/config_manager.py +496 -0
  29. snowflake/connector/connection.py +2273 -0
  30. snowflake/connector/connection_diagnostic.py +776 -0
  31. snowflake/connector/constants.py +442 -0
  32. snowflake/connector/converter.py +785 -0
  33. snowflake/connector/converter_issue23517.py +87 -0
  34. snowflake/connector/converter_null.py +14 -0
  35. snowflake/connector/converter_snowsql.py +205 -0
  36. snowflake/connector/cursor.py +1951 -0
  37. snowflake/connector/dbapi.py +53 -0
  38. snowflake/connector/description.py +19 -0
  39. snowflake/connector/direct_file_operation_utils.py +88 -0
  40. snowflake/connector/encryption_util.py +220 -0
  41. snowflake/connector/errorcode.py +91 -0
  42. snowflake/connector/errors.py +615 -0
  43. snowflake/connector/externals_utils/__init__.py +0 -0
  44. snowflake/connector/externals_utils/externals_setup.py +27 -0
  45. snowflake/connector/feature.py +4 -0
  46. snowflake/connector/file_compression_type.py +118 -0
  47. snowflake/connector/file_lock.py +72 -0
  48. snowflake/connector/file_transfer_agent.py +1215 -0
  49. snowflake/connector/file_util.py +153 -0
  50. snowflake/connector/gcs_storage_client.py +474 -0
  51. snowflake/connector/gzip_decoder.py +85 -0
  52. snowflake/connector/local_storage_client.py +90 -0
  53. snowflake/connector/log_configuration.py +60 -0
  54. snowflake/connector/logging_utils/__init__.py +0 -0
  55. snowflake/connector/logging_utils/filters.py +72 -0
  56. snowflake/connector/nanoarrow_arrow_iterator.cp313-win_amd64.pyd +0 -0
  57. snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.cpp +60 -0
  58. snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.hpp +29 -0
  59. snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.cpp +19 -0
  60. snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.hpp +26 -0
  61. snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.cpp +21 -0
  62. snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.hpp +23 -0
  63. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.cpp +557 -0
  64. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.hpp +98 -0
  65. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.cpp +125 -0
  66. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.hpp +115 -0
  67. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.cpp +1005 -0
  68. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.hpp +127 -0
  69. snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.cpp +47 -0
  70. snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.hpp +46 -0
  71. snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.cpp +83 -0
  72. snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.hpp +35 -0
  73. snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.cpp +97 -0
  74. snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.hpp +72 -0
  75. snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.cpp +73 -0
  76. snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.hpp +28 -0
  77. snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.cpp +30 -0
  78. snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.hpp +35 -0
  79. snowflake/connector/nanoarrow_cpp/ArrowIterator/IColumnConverter.hpp +17 -0
  80. snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.cpp +23 -0
  81. snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.hpp +45 -0
  82. snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.cpp +71 -0
  83. snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.hpp +56 -0
  84. snowflake/connector/nanoarrow_cpp/ArrowIterator/LICENSE.txt +209 -0
  85. snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.cpp +75 -0
  86. snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.hpp +30 -0
  87. snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.cpp +46 -0
  88. snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.hpp +29 -0
  89. snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.cpp +8 -0
  90. snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.hpp +95 -0
  91. snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.cpp +57 -0
  92. snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.hpp +36 -0
  93. snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.cpp +34 -0
  94. snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.hpp +48 -0
  95. snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.cpp +19 -0
  96. snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.hpp +26 -0
  97. snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.cpp +36 -0
  98. snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.hpp +31 -0
  99. snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.cpp +346 -0
  100. snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.hpp +145 -0
  101. snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/macros.hpp +14 -0
  102. snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.cpp +65 -0
  103. snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.hpp +68 -0
  104. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_accessors.h +101 -0
  105. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_alloc.h +127 -0
  106. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_assert.h +45 -0
  107. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_builder.h +1908 -0
  108. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_emitter.h +215 -0
  109. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_endian.h +125 -0
  110. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_epilogue.h +7 -0
  111. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_flatbuffers.h +55 -0
  112. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_identifier.h +148 -0
  113. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_iov.h +31 -0
  114. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_prologue.h +8 -0
  115. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_refmap.h +144 -0
  116. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_rtconfig.h +162 -0
  117. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_types.h +97 -0
  118. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_verifier.h +239 -0
  119. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/flatcc_portable.h +14 -0
  120. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/paligned_alloc.h +210 -0
  121. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pattributes.h +84 -0
  122. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic.h +84 -0
  123. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_pop.h +20 -0
  124. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_push.h +51 -0
  125. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian.h +206 -0
  126. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian_detect.h +118 -0
  127. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinline.h +19 -0
  128. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinttypes.h +52 -0
  129. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable.h +2 -0
  130. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable_basic.h +25 -0
  131. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstatic_assert.h +67 -0
  132. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdalign.h +162 -0
  133. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdint.h +898 -0
  134. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/punaligned.h +190 -0
  135. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pversion.h +6 -0
  136. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pwarnings.h +52 -0
  137. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc.c +3204 -0
  138. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.c +3217 -0
  139. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.h +3618 -0
  140. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.hpp +379 -0
  141. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_arrow_iterator.pyx +256 -0
  142. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.c +512 -0
  143. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.h +350 -0
  144. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.c +33273 -0
  145. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.h +397 -0
  146. snowflake/connector/nanoarrow_cpp/Logging/logging.cpp +100 -0
  147. snowflake/connector/nanoarrow_cpp/Logging/logging.hpp +49 -0
  148. snowflake/connector/network.py +1297 -0
  149. snowflake/connector/ocsp_asn1crypto.py +447 -0
  150. snowflake/connector/ocsp_snowflake.py +1883 -0
  151. snowflake/connector/options.py +131 -0
  152. snowflake/connector/pandas_tools.py +732 -0
  153. snowflake/connector/proxy.py +43 -0
  154. snowflake/connector/py.typed +0 -0
  155. snowflake/connector/result_batch.py +786 -0
  156. snowflake/connector/result_set.py +319 -0
  157. snowflake/connector/s3_storage_client.py +605 -0
  158. snowflake/connector/secret_detector.py +181 -0
  159. snowflake/connector/sf_dirs.py +64 -0
  160. snowflake/connector/sfbinaryformat.py +35 -0
  161. snowflake/connector/sfdatetime.py +345 -0
  162. snowflake/connector/snow_logging.py +144 -0
  163. snowflake/connector/sqlstate.py +9 -0
  164. snowflake/connector/ssd_internal_keys.py +30 -0
  165. snowflake/connector/ssl_wrap_socket.py +134 -0
  166. snowflake/connector/storage_client.py +478 -0
  167. snowflake/connector/telemetry.py +249 -0
  168. snowflake/connector/telemetry_oob.py +543 -0
  169. snowflake/connector/test_util.py +30 -0
  170. snowflake/connector/time_util.py +159 -0
  171. snowflake/connector/token_cache.py +402 -0
  172. snowflake/connector/tool/__init__.py +0 -0
  173. snowflake/connector/tool/dump_certs.py +57 -0
  174. snowflake/connector/tool/dump_ocsp_response.py +139 -0
  175. snowflake/connector/tool/dump_ocsp_response_cache.py +194 -0
  176. snowflake/connector/tool/probe_connection.py +69 -0
  177. snowflake/connector/url_util.py +49 -0
  178. snowflake/connector/util_text.py +301 -0
  179. snowflake/connector/vendored/__init__.py +3 -0
  180. snowflake/connector/vendored/requests/LICENSE +175 -0
  181. snowflake/connector/vendored/requests/__init__.py +169 -0
  182. snowflake/connector/vendored/requests/__version__.py +14 -0
  183. snowflake/connector/vendored/requests/_internal_utils.py +50 -0
  184. snowflake/connector/vendored/requests/adapters.py +537 -0
  185. snowflake/connector/vendored/requests/api.py +157 -0
  186. snowflake/connector/vendored/requests/auth.py +315 -0
  187. snowflake/connector/vendored/requests/certs.py +17 -0
  188. snowflake/connector/vendored/requests/compat.py +79 -0
  189. snowflake/connector/vendored/requests/cookies.py +561 -0
  190. snowflake/connector/vendored/requests/exceptions.py +140 -0
  191. snowflake/connector/vendored/requests/help.py +134 -0
  192. snowflake/connector/vendored/requests/hooks.py +33 -0
  193. snowflake/connector/vendored/requests/models.py +1033 -0
  194. snowflake/connector/vendored/requests/sessions.py +833 -0
  195. snowflake/connector/vendored/requests/status_codes.py +128 -0
  196. snowflake/connector/vendored/requests/structures.py +99 -0
  197. snowflake/connector/vendored/requests/utils.py +1093 -0
  198. snowflake/connector/vendored/urllib3/LICENSE.txt +21 -0
  199. snowflake/connector/vendored/urllib3/__init__.py +85 -0
  200. snowflake/connector/vendored/urllib3/_collections.py +355 -0
  201. snowflake/connector/vendored/urllib3/_version.py +2 -0
  202. snowflake/connector/vendored/urllib3/connection.py +572 -0
  203. snowflake/connector/vendored/urllib3/connectionpool.py +1137 -0
  204. snowflake/connector/vendored/urllib3/contrib/__init__.py +0 -0
  205. snowflake/connector/vendored/urllib3/contrib/_appengine_environ.py +36 -0
  206. snowflake/connector/vendored/urllib3/contrib/_securetransport/__init__.py +0 -0
  207. snowflake/connector/vendored/urllib3/contrib/_securetransport/bindings.py +519 -0
  208. snowflake/connector/vendored/urllib3/contrib/_securetransport/low_level.py +397 -0
  209. snowflake/connector/vendored/urllib3/contrib/appengine.py +314 -0
  210. snowflake/connector/vendored/urllib3/contrib/ntlmpool.py +130 -0
  211. snowflake/connector/vendored/urllib3/contrib/pyopenssl.py +509 -0
  212. snowflake/connector/vendored/urllib3/contrib/securetransport.py +920 -0
  213. snowflake/connector/vendored/urllib3/contrib/socks.py +216 -0
  214. snowflake/connector/vendored/urllib3/exceptions.py +323 -0
  215. snowflake/connector/vendored/urllib3/fields.py +274 -0
  216. snowflake/connector/vendored/urllib3/filepost.py +98 -0
  217. snowflake/connector/vendored/urllib3/packages/__init__.py +0 -0
  218. snowflake/connector/vendored/urllib3/packages/backports/__init__.py +0 -0
  219. snowflake/connector/vendored/urllib3/packages/backports/makefile.py +51 -0
  220. snowflake/connector/vendored/urllib3/packages/backports/weakref_finalize.py +155 -0
  221. snowflake/connector/vendored/urllib3/packages/six.py +1076 -0
  222. snowflake/connector/vendored/urllib3/poolmanager.py +540 -0
  223. snowflake/connector/vendored/urllib3/request.py +191 -0
  224. snowflake/connector/vendored/urllib3/response.py +885 -0
  225. snowflake/connector/vendored/urllib3/util/__init__.py +49 -0
  226. snowflake/connector/vendored/urllib3/util/connection.py +156 -0
  227. snowflake/connector/vendored/urllib3/util/proxy.py +57 -0
  228. snowflake/connector/vendored/urllib3/util/queue.py +22 -0
  229. snowflake/connector/vendored/urllib3/util/request.py +146 -0
  230. snowflake/connector/vendored/urllib3/util/response.py +107 -0
  231. snowflake/connector/vendored/urllib3/util/retry.py +620 -0
  232. snowflake/connector/vendored/urllib3/util/ssl_.py +495 -0
  233. snowflake/connector/vendored/urllib3/util/ssl_match_hostname.py +159 -0
  234. snowflake/connector/vendored/urllib3/util/ssltransport.py +221 -0
  235. snowflake/connector/vendored/urllib3/util/timeout.py +271 -0
  236. snowflake/connector/vendored/urllib3/util/url.py +435 -0
  237. snowflake/connector/vendored/urllib3/util/wait.py +152 -0
  238. snowflake/connector/version.py +3 -0
  239. snowflake/connector/wif_util.py +407 -0
  240. snowflake_connector_python-3.16.0.dist-info/METADATA +1475 -0
  241. snowflake_connector_python-3.16.0.dist-info/RECORD +246 -0
  242. snowflake_connector_python-3.16.0.dist-info/WHEEL +5 -0
  243. snowflake_connector_python-3.16.0.dist-info/entry_points.txt +4 -0
  244. snowflake_connector_python-3.16.0.dist-info/licenses/LICENSE.txt +202 -0
  245. snowflake_connector_python-3.16.0.dist-info/licenses/NOTICE +8 -0
  246. 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)}"