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,605 @@
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
+ use_accelerate_endpoint = self._get_bucket_accelerate_config(
124
+ self.s3location.bucket_name
125
+ )
126
+
127
+ if use_accelerate_endpoint:
128
+ self.endpoint = (
129
+ f"https://{self.s3location.bucket_name}.s3-accelerate.amazonaws.com"
130
+ )
131
+ else:
132
+ self.endpoint = (
133
+ f"https://{self.s3location.bucket_name}.s3.amazonaws.com"
134
+ )
135
+ return use_accelerate_endpoint
136
+
137
+ @staticmethod
138
+ def _sign_bytes(secret_key: bytes, _input: str) -> bytes:
139
+ """Applies HMAC-SHA-256 to given string with secret_key."""
140
+ h = hmac.HMAC(secret_key, hashes.SHA256())
141
+ h.update(_input.encode("utf-8"))
142
+ return h.finalize()
143
+
144
+ @staticmethod
145
+ def _sign_bytes_hex(secret_key: bytes, _input: str) -> bytes:
146
+ """Convenience function, same as _sign_bytes, but returns result in hex form."""
147
+ return binascii.hexlify(SnowflakeS3RestClient._sign_bytes(secret_key, _input))
148
+
149
+ @staticmethod
150
+ def _hash_bytes(_input: bytes) -> bytes:
151
+ """Applies SHA-256 hash to given bytes."""
152
+ digest = hashes.Hash(hashes.SHA256())
153
+ digest.update(_input)
154
+ return digest.finalize()
155
+
156
+ @staticmethod
157
+ def _hash_bytes_hex(_input: bytes) -> bytes:
158
+ """Convenience function, same as _hash_bytes, but returns result in hex form."""
159
+ return binascii.hexlify(SnowflakeS3RestClient._hash_bytes(_input))
160
+
161
+ @staticmethod
162
+ def _construct_query_string(
163
+ query_parts: tuple[tuple[str, str], ...],
164
+ ) -> str:
165
+ """Convenience function to build the query part of a URL from key-value pairs.
166
+
167
+ It filters out empty strings from the key, value pairs.
168
+ """
169
+ return "&".join(["=".join(filter(bool, e)) for e in query_parts])
170
+
171
+ @staticmethod
172
+ def _construct_canonicalized_and_signed_headers(
173
+ headers: dict[str, str | list[str]]
174
+ ) -> tuple[str, str]:
175
+ """Construct canonical headers as per AWS specs, returns the signed headers too.
176
+
177
+ Does not support sorting by values in case the keys are the same, don't send
178
+ in duplicate keys, but this is not possible with a dictionary anyways.
179
+ """
180
+ res = []
181
+ low_key_dict = {k.lower(): v for k, v in headers.items()}
182
+ sorted_headers = sorted(low_key_dict.keys())
183
+ _res = [(k, low_key_dict[k]) for k in sorted_headers]
184
+
185
+ for k, v in _res:
186
+ # if value is a list, convert to string delimited by comma
187
+ if isinstance(v, list):
188
+ v = ",".join(v)
189
+ # if multiline header, replace withs space
190
+ k = k.replace("\n", " ")
191
+ res.append(k.strip() + ":" + RE_MULTIPLE_SPACES.sub(" ", v.strip()))
192
+
193
+ ans = "\n".join(res)
194
+ if ans:
195
+ ans += "\n"
196
+
197
+ return ans, ";".join(sorted_headers)
198
+
199
+ @staticmethod
200
+ def _construct_canonical_request_and_signed_headers(
201
+ verb: str,
202
+ canonical_uri_parameter: str,
203
+ query_parts: dict[str, str],
204
+ canonical_headers: dict[str, str | list[str]] | None = None,
205
+ payload_hash: str = "",
206
+ ) -> tuple[str, str]:
207
+ """Build canonical request and also return signed headers.
208
+
209
+ Note: this doesn't support sorting by values in case the same key is given
210
+ more than once, but doing this is also not possible with a dictionary.
211
+ """
212
+ canonical_query_string = "&".join(
213
+ "=".join([k, v]) for k, v in sorted(query_parts.items(), key=itemgetter(0))
214
+ )
215
+ (
216
+ canonical_headers,
217
+ signed_headers,
218
+ ) = SnowflakeS3RestClient._construct_canonicalized_and_signed_headers(
219
+ canonical_headers
220
+ )
221
+
222
+ return (
223
+ "\n".join(
224
+ [
225
+ verb,
226
+ canonical_uri_parameter or "/",
227
+ canonical_query_string,
228
+ canonical_headers,
229
+ signed_headers,
230
+ payload_hash,
231
+ ]
232
+ ),
233
+ signed_headers,
234
+ )
235
+
236
+ @staticmethod
237
+ def _construct_string_to_sign(
238
+ region_name: str,
239
+ service_name: str,
240
+ amzdate: str,
241
+ short_amzdate: str,
242
+ canonical_request_hash: bytes,
243
+ ) -> tuple[str, str]:
244
+ """Given all the necessary information construct a V4 string to sign.
245
+
246
+ As per AWS specs it requires the scope, the hash of the canonical request and
247
+ the current date in the following format: YYYYMMDDTHHMMSSZ where T and Z are
248
+ constant characters.
249
+ This function generates the scope from the amzdate (which is just the date
250
+ portion of amzdate), region name and service we want to use (this is only s3
251
+ in our case).
252
+ """
253
+ scope = f"{short_amzdate}/{region_name}/{service_name}/aws4_request"
254
+ return (
255
+ "\n".join(
256
+ [
257
+ "AWS4-HMAC-SHA256",
258
+ amzdate,
259
+ scope,
260
+ canonical_request_hash.decode("utf-8"),
261
+ ]
262
+ ),
263
+ scope,
264
+ )
265
+
266
+ def _has_expired_token(self, response: requests.Response) -> bool:
267
+ """Extract error code and error message from the S3's error response.
268
+
269
+ Expected format:
270
+ https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#RESTErrorResponses
271
+
272
+ Args:
273
+ response: Rest error response in XML format
274
+
275
+ Returns: True if the error response is caused by token expiration
276
+
277
+ """
278
+ if response.status_code != 400:
279
+ return False
280
+ message = response.text
281
+ if not message or message.isspace():
282
+ return False
283
+ err = ET.fromstring(message)
284
+ return err.find("Code").text == EXPIRED_TOKEN
285
+
286
+ @staticmethod
287
+ def _extract_bucket_name_and_path(stage_location) -> S3Location:
288
+ # split stage location as bucket name and path
289
+ bucket_name, _, path = stage_location.partition("/")
290
+ if path and not path.endswith("/"):
291
+ path += "/"
292
+
293
+ return S3Location(bucket_name=bucket_name, path=path)
294
+
295
+ def _send_request_with_authentication_and_retry(
296
+ self,
297
+ url: str,
298
+ verb: str,
299
+ retry_id: int | str,
300
+ query_parts: dict[str, str] | None = None,
301
+ x_amz_headers: dict[str, str] | None = None,
302
+ headers: dict[str, str] | None = None,
303
+ payload: bytes | bytearray | IOBase | None = None,
304
+ unsigned_payload: bool = False,
305
+ ignore_content_encoding: bool = False,
306
+ ) -> requests.Response:
307
+ if x_amz_headers is None:
308
+ x_amz_headers = {}
309
+ if headers is None:
310
+ headers = {}
311
+ if payload is None:
312
+ payload = b""
313
+ if query_parts is None:
314
+ query_parts = {}
315
+ parsed_url = urlparse(url)
316
+ x_amz_headers["x-amz-security-token"] = self.credentials.creds.get(
317
+ "AWS_TOKEN", ""
318
+ )
319
+ x_amz_headers["host"] = parsed_url.hostname
320
+ if unsigned_payload:
321
+ x_amz_headers["x-amz-content-sha256"] = UNSIGNED_PAYLOAD
322
+ else:
323
+ x_amz_headers["x-amz-content-sha256"] = (
324
+ SnowflakeS3RestClient._hash_bytes_hex(payload).lower().decode()
325
+ )
326
+
327
+ def generate_authenticated_url_and_args_v4() -> tuple[bytes, dict[str, bytes]]:
328
+ t = datetime.now(timezone.utc).replace(tzinfo=None)
329
+ amzdate = t.strftime("%Y%m%dT%H%M%SZ")
330
+ short_amzdate = amzdate[:8]
331
+ x_amz_headers["x-amz-date"] = amzdate
332
+ x_amz_headers["x-amz-security-token"] = self.credentials.creds.get(
333
+ "AWS_TOKEN", ""
334
+ )
335
+
336
+ (
337
+ canonical_request,
338
+ signed_headers,
339
+ ) = self._construct_canonical_request_and_signed_headers(
340
+ verb=verb,
341
+ canonical_uri_parameter=parsed_url.path
342
+ + (f";{parsed_url.params}" if parsed_url.params else ""),
343
+ query_parts=query_parts,
344
+ canonical_headers=x_amz_headers,
345
+ payload_hash=x_amz_headers["x-amz-content-sha256"],
346
+ )
347
+ string_to_sign, scope = self._construct_string_to_sign(
348
+ self.region_name,
349
+ "s3",
350
+ amzdate,
351
+ short_amzdate,
352
+ self._hash_bytes_hex(canonical_request.encode("utf-8")).lower(),
353
+ )
354
+ kDate = self._sign_bytes(
355
+ ("AWS4" + self.credentials.creds["AWS_SECRET_KEY"]).encode("utf-8"),
356
+ short_amzdate,
357
+ )
358
+ kRegion = self._sign_bytes(kDate, self.region_name)
359
+ kService = self._sign_bytes(kRegion, "s3")
360
+ signing_key = self._sign_bytes(kService, "aws4_request")
361
+
362
+ signature = self._sign_bytes_hex(signing_key, string_to_sign).lower()
363
+ authorization_header = (
364
+ "AWS4-HMAC-SHA256 "
365
+ + f"Credential={self.credentials.creds['AWS_KEY_ID']}/{scope}, "
366
+ + f"SignedHeaders={signed_headers}, "
367
+ + f"Signature={signature.decode('utf-8')}"
368
+ )
369
+ headers.update(x_amz_headers)
370
+ headers["Authorization"] = authorization_header
371
+ rest_args = {"headers": headers}
372
+
373
+ if payload:
374
+ rest_args["data"] = payload
375
+
376
+ # add customized hook: to remove content-encoding from response.
377
+ if ignore_content_encoding:
378
+ rest_args["hooks"] = {"response": remove_content_encoding}
379
+
380
+ return url.encode("utf-8"), rest_args
381
+
382
+ return self._send_request_with_retry(
383
+ verb, generate_authenticated_url_and_args_v4, retry_id
384
+ )
385
+
386
+ def get_file_header(self, filename: str) -> FileHeader | None:
387
+ """Gets the metadata of file in specified location.
388
+
389
+ Args:
390
+ filename: Name of remote file.
391
+
392
+ Returns:
393
+ None if HEAD returns 404, otherwise a FileHeader instance populated
394
+ with metadata
395
+ """
396
+ path = quote(self.s3location.path + filename.lstrip("/"))
397
+ url = self.endpoint + f"/{path}"
398
+
399
+ retry_id = "HEAD"
400
+ self.retry_count[retry_id] = 0
401
+ response = self._send_request_with_authentication_and_retry(
402
+ url=url, verb="HEAD", retry_id=retry_id
403
+ )
404
+ if response.status_code == 200:
405
+ self.meta.result_status = ResultStatus.UPLOADED
406
+ metadata = response.headers
407
+ encryption_metadata = (
408
+ EncryptionMetadata(
409
+ key=metadata.get(META_PREFIX + AMZ_KEY),
410
+ iv=metadata.get(META_PREFIX + AMZ_IV),
411
+ matdesc=metadata.get(META_PREFIX + AMZ_MATDESC),
412
+ )
413
+ if metadata.get(META_PREFIX + AMZ_KEY)
414
+ else None
415
+ )
416
+ return FileHeader(
417
+ digest=metadata.get(META_PREFIX + SFC_DIGEST),
418
+ content_length=int(metadata.get("Content-Length")),
419
+ encryption_metadata=encryption_metadata,
420
+ )
421
+ elif response.status_code == 404:
422
+ logger.debug(
423
+ f"not found. bucket: {self.s3location.bucket_name}, path: {path}"
424
+ )
425
+ self.meta.result_status = ResultStatus.NOT_FOUND_FILE
426
+ return None
427
+ else:
428
+ response.raise_for_status()
429
+
430
+ def _prepare_file_metadata(self) -> dict[str, Any]:
431
+ """Construct metadata for a file to be uploaded.
432
+
433
+ Returns: File metadata in a dict.
434
+
435
+ """
436
+ s3_metadata = {
437
+ META_PREFIX + SFC_DIGEST: self.meta.sha256_digest,
438
+ }
439
+ if self.encryption_metadata:
440
+ s3_metadata.update(
441
+ {
442
+ META_PREFIX + AMZ_IV: self.encryption_metadata.iv,
443
+ META_PREFIX + AMZ_KEY: self.encryption_metadata.key,
444
+ META_PREFIX + AMZ_MATDESC: self.encryption_metadata.matdesc,
445
+ }
446
+ )
447
+ return s3_metadata
448
+
449
+ def _initiate_multipart_upload(self) -> None:
450
+ query_parts = (("uploads", ""),)
451
+ path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
452
+ query_string = self._construct_query_string(query_parts)
453
+ url = self.endpoint + f"/{path}?{query_string}"
454
+ s3_metadata = self._prepare_file_metadata()
455
+ # initiate multipart upload
456
+ retry_id = "Initiate"
457
+ self.retry_count[retry_id] = 0
458
+ response = self._send_request_with_authentication_and_retry(
459
+ url=url,
460
+ verb="POST",
461
+ retry_id=retry_id,
462
+ x_amz_headers=s3_metadata,
463
+ headers={HTTP_HEADER_CONTENT_TYPE: HTTP_HEADER_VALUE_OCTET_STREAM},
464
+ query_parts=dict(query_parts),
465
+ )
466
+ if response.status_code == 200:
467
+ self.upload_id = ET.fromstring(response.content)[2].text
468
+ self.etags = [None] * self.num_of_chunks
469
+ else:
470
+ response.raise_for_status()
471
+
472
+ def _upload_chunk(self, chunk_id: int, chunk: bytes) -> None:
473
+ path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
474
+ url = self.endpoint + f"/{path}"
475
+
476
+ if self.num_of_chunks == 1: # single request
477
+ s3_metadata = self._prepare_file_metadata()
478
+ response = self._send_request_with_authentication_and_retry(
479
+ url=url,
480
+ verb="PUT",
481
+ retry_id=chunk_id,
482
+ payload=chunk,
483
+ x_amz_headers=s3_metadata,
484
+ headers={HTTP_HEADER_CONTENT_TYPE: HTTP_HEADER_VALUE_OCTET_STREAM},
485
+ unsigned_payload=True,
486
+ )
487
+ response.raise_for_status()
488
+ else:
489
+ # multipart PUT
490
+ query_parts = (
491
+ ("partNumber", str(chunk_id + 1)),
492
+ ("uploadId", self.upload_id),
493
+ )
494
+ query_string = self._construct_query_string(query_parts)
495
+ chunk_url = f"{url}?{query_string}"
496
+ response = self._send_request_with_authentication_and_retry(
497
+ url=chunk_url,
498
+ verb="PUT",
499
+ retry_id=chunk_id,
500
+ payload=chunk,
501
+ unsigned_payload=True,
502
+ query_parts=dict(query_parts),
503
+ )
504
+ if response.status_code == 200:
505
+ self.etags[chunk_id] = response.headers["ETag"]
506
+ response.raise_for_status()
507
+
508
+ def _complete_multipart_upload(self) -> None:
509
+ query_parts = (("uploadId", self.upload_id),)
510
+ path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
511
+ query_string = self._construct_query_string(query_parts)
512
+ url = self.endpoint + f"/{path}?{query_string}"
513
+ logger.debug("Initiating multipart upload complete")
514
+ # Complete multipart upload
515
+ root = ET.Element("CompleteMultipartUpload")
516
+ for idx, etag_str in enumerate(self.etags):
517
+ part = ET.Element("Part")
518
+ etag = ET.Element("ETag")
519
+ etag.text = etag_str
520
+ part.append(etag)
521
+ part_number = ET.Element("PartNumber")
522
+ part_number.text = str(idx + 1)
523
+ part.append(part_number)
524
+ root.append(part)
525
+ retry_id = "Complete"
526
+ self.retry_count[retry_id] = 0
527
+ response = self._send_request_with_authentication_and_retry(
528
+ url=url,
529
+ verb="POST",
530
+ retry_id=retry_id,
531
+ payload=ET.tostring(root),
532
+ query_parts=dict(query_parts),
533
+ )
534
+ response.raise_for_status()
535
+
536
+ def _abort_multipart_upload(self) -> None:
537
+ if self.upload_id is None:
538
+ return
539
+ query_parts = (("uploadId", self.upload_id),)
540
+ path = quote(self.s3location.path + self.meta.dst_file_name.lstrip("/"))
541
+ query_string = self._construct_query_string(query_parts)
542
+ url = self.endpoint + f"/{path}?{query_string}"
543
+
544
+ retry_id = "Abort"
545
+ self.retry_count[retry_id] = 0
546
+ response = self._send_request_with_authentication_and_retry(
547
+ url=url,
548
+ verb="DELETE",
549
+ retry_id=retry_id,
550
+ query_parts=dict(query_parts),
551
+ )
552
+ response.raise_for_status()
553
+
554
+ def download_chunk(self, chunk_id: int) -> None:
555
+ logger.debug(f"Downloading chunk {chunk_id}")
556
+ path = quote(self.s3location.path + self.meta.src_file_name.lstrip("/"))
557
+ url = self.endpoint + f"/{path}"
558
+ if self.num_of_chunks == 1:
559
+ response = self._send_request_with_authentication_and_retry(
560
+ url=url,
561
+ verb="GET",
562
+ retry_id=chunk_id,
563
+ ignore_content_encoding=True,
564
+ )
565
+ if response.status_code == 200:
566
+ self.write_downloaded_chunk(0, response.content)
567
+ self.meta.result_status = ResultStatus.DOWNLOADED
568
+ response.raise_for_status()
569
+ else:
570
+ chunk_size = self.chunk_size
571
+ if chunk_id < self.num_of_chunks - 1:
572
+ _range = f"{chunk_id * chunk_size}-{(chunk_id+1)*chunk_size-1}"
573
+ else:
574
+ _range = f"{chunk_id * chunk_size}-"
575
+
576
+ response = self._send_request_with_authentication_and_retry(
577
+ url=url,
578
+ verb="GET",
579
+ retry_id=chunk_id,
580
+ headers={"Range": f"bytes={_range}"},
581
+ )
582
+ if response.status_code in (200, 206):
583
+ self.write_downloaded_chunk(chunk_id, response.content)
584
+ response.raise_for_status()
585
+
586
+ def _get_bucket_accelerate_config(self, bucket_name: str) -> bool:
587
+ query_parts = (("accelerate", ""),)
588
+ query_string = self._construct_query_string(query_parts)
589
+ url = f"https://{bucket_name}.s3.amazonaws.com/?{query_string}"
590
+ retry_id = "accelerate"
591
+ self.retry_count[retry_id] = 0
592
+ response = self._send_request_with_authentication_and_retry(
593
+ url=url, verb="GET", retry_id=retry_id, query_parts=dict(query_parts)
594
+ )
595
+ if response.status_code == 200:
596
+ config = ET.fromstring(response.text)
597
+ namespace = config.tag[: config.tag.index("}") + 1]
598
+ statusTag = f"{namespace}Status"
599
+ found = config.find(statusTag)
600
+ use_accelerate_endpoint = (
601
+ False if found is None else (found.text == "Enabled")
602
+ )
603
+ logger.debug(f"use_accelerate_endpoint: {use_accelerate_endpoint}")
604
+ return use_accelerate_endpoint
605
+ return False