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,1215 @@
1
+ #!/usr/bin/env python
2
+ from __future__ import annotations
3
+
4
+ import binascii
5
+ import glob
6
+ import math
7
+ import mimetypes
8
+ import os
9
+ import sys
10
+ import threading
11
+ from concurrent.futures.thread import ThreadPoolExecutor
12
+ from dataclasses import dataclass
13
+ from functools import partial
14
+ from logging import getLogger
15
+ from time import time
16
+ from typing import IO, TYPE_CHECKING, Any, Callable, TypeVar
17
+
18
+ from .azure_storage_client import SnowflakeAzureRestClient
19
+ from .compat import IS_WINDOWS
20
+ from .constants import (
21
+ AZURE_CHUNK_SIZE,
22
+ AZURE_FS,
23
+ CMD_TYPE_DOWNLOAD,
24
+ CMD_TYPE_UPLOAD,
25
+ GCS_FS,
26
+ LOCAL_FS,
27
+ S3_DEFAULT_CHUNK_SIZE,
28
+ S3_FS,
29
+ S3_MAX_OBJECT_SIZE,
30
+ S3_MAX_PARTS,
31
+ S3_MIN_PART_SIZE,
32
+ ResultStatus,
33
+ megabyte,
34
+ )
35
+ from .converter_snowsql import SnowflakeConverterSnowSQL
36
+ from .errorcode import (
37
+ ER_COMPRESSION_NOT_SUPPORTED,
38
+ ER_FAILED_TO_DOWNLOAD_FROM_STAGE,
39
+ ER_FAILED_TO_UPLOAD_TO_STAGE,
40
+ ER_FILE_NOT_EXISTS,
41
+ ER_INTERNAL_NOT_MATCH_ENCRYPT_MATERIAL,
42
+ ER_INVALID_STAGE_FS,
43
+ ER_INVALID_STAGE_LOCATION,
44
+ ER_LOCAL_PATH_NOT_DIRECTORY,
45
+ )
46
+ from .errors import (
47
+ DatabaseError,
48
+ Error,
49
+ InternalError,
50
+ OperationalError,
51
+ ProgrammingError,
52
+ )
53
+ from .file_compression_type import CompressionTypes, lookup_by_mime_sub_type
54
+ from .gcs_storage_client import SnowflakeGCSRestClient
55
+ from .local_storage_client import SnowflakeLocalStorageClient
56
+ from .s3_storage_client import SnowflakeS3RestClient
57
+ from .storage_client import SnowflakeFileEncryptionMaterial, SnowflakeStorageClient
58
+
59
+ if TYPE_CHECKING: # pragma: no cover
60
+ from .connection import SnowflakeConnection
61
+ from .cursor import SnowflakeCursor
62
+ from .file_compression_type import CompressionType
63
+
64
+ VALID_STORAGE = [LOCAL_FS, S3_FS, AZURE_FS, GCS_FS]
65
+
66
+ INJECT_WAIT_IN_PUT = 0
67
+
68
+ logger = getLogger(__name__)
69
+
70
+
71
+ def result_text_column_desc(name: str) -> dict[str, Any]:
72
+ return {
73
+ "name": name,
74
+ "type": "text",
75
+ "length": 16777216,
76
+ "precision": None,
77
+ "scale": None,
78
+ "nullable": False,
79
+ }
80
+
81
+
82
+ def result_fixed_column_desc(name: str) -> dict[str, Any]:
83
+ return {
84
+ "name": name,
85
+ "type": "fixed",
86
+ "length": 5,
87
+ "precision": 0,
88
+ "scale": 0,
89
+ "nullable": False,
90
+ }
91
+
92
+
93
+ # TODO: rewrite, we use this class to store information about file transfers
94
+ # It'd make more sense to define a new object, like FileTransferMeta that then has
95
+ # more FileMetas inside of it. This would help in some cases, where for example
96
+ # consider the case where we run into an unrecoverable error for the whole transfer
97
+ # job and we need to convey an error to the main thread and that error needs to be
98
+ # raised by the main thread. Where should this go? Currently the answer could be
99
+ # all of the current FileMetas. Hmmm...
100
+ @dataclass
101
+ class SnowflakeFileMeta:
102
+ """Class to keep track of information necessary for file operations."""
103
+
104
+ name: str
105
+ src_file_name: str
106
+ stage_location_type: str
107
+ result_status: ResultStatus | None = None
108
+
109
+ sfagent: SnowflakeFileTransferAgent | None = None
110
+ put_callback: type[SnowflakeProgressPercentage] | None = None
111
+ put_azure_callback: type[SnowflakeProgressPercentage] | None = None
112
+ put_callback_output_stream: IO[str] | None = None
113
+ get_callback: type[SnowflakeProgressPercentage] | None = None
114
+ get_azure_callback: type[SnowflakeProgressPercentage] | None = None
115
+ get_callback_output_stream: IO[str] | None = None
116
+ show_progress_bar: bool = False
117
+ multipart_threshold: int = 67108864 # Historical value
118
+ presigned_url: str | None = None
119
+ overwrite: bool = False
120
+ sha256_digest: str | None = None
121
+ upload_size: int | None = None
122
+ real_src_file_name: str | None = None
123
+ error_details: Exception | None = None
124
+ last_error: Exception | None = None
125
+ no_sleeping_time: bool = False
126
+ gcs_file_header_digest: str | None = None
127
+ gcs_file_header_content_length: int | None = None
128
+ gcs_file_header_encryption_metadata: dict[str, Any] | None = None
129
+
130
+ encryption_material: SnowflakeFileEncryptionMaterial | None = None
131
+ # Specific to Uploads only
132
+ src_file_size: int = 0
133
+ src_compression_type: CompressionType | None = None
134
+ dst_compression_type: CompressionType = None
135
+ require_compress: bool = False
136
+ dst_file_name: str | None = None
137
+ dst_file_size: int = -1
138
+ intermediate_stream: IO[bytes] | None = None
139
+ src_stream: IO[bytes] | None = None
140
+ skip_upload_on_content_match: bool = False
141
+ # Specific to Downloads only
142
+ local_location: str | None = None
143
+
144
+
145
+ def _update_progress(
146
+ file_name: str,
147
+ start_time: float,
148
+ total_size: float,
149
+ progress: float | int,
150
+ output_stream: IO | None = sys.stdout,
151
+ show_progress_bar: bool | None = True,
152
+ ) -> bool:
153
+ bar_length = 10 # Modify this to change the length of the progress bar
154
+ total_size /= megabyte
155
+ status = ""
156
+ elapsed_time = time() - start_time
157
+ throughput = (total_size / elapsed_time) if elapsed_time != 0.0 else 0.0
158
+ if isinstance(progress, int):
159
+ progress = float(progress)
160
+ if not isinstance(progress, float):
161
+ progress = 0
162
+ status = "error: progress var must be float\r\n"
163
+ if progress < 0:
164
+ progress = 0
165
+ status = "Halt...\r\n"
166
+ if progress >= 1:
167
+ progress = 1
168
+ status = f"Done ({elapsed_time:.3f}s, {throughput:.2f}MB/s).\r\n"
169
+ if not status and show_progress_bar:
170
+ status = f"({elapsed_time:.3f}s, {throughput:.2f}MB/s)"
171
+ if status:
172
+ block = int(round(bar_length * progress))
173
+
174
+ text = (
175
+ f"\r{file_name}({total_size:.2f}MB): "
176
+ f"[{'#' * block + '-' * (bar_length - block)}] "
177
+ f"{progress * 100.0:.2f}% {status}"
178
+ )
179
+ output_stream.write(text)
180
+ output_stream.flush()
181
+ logger.debug(
182
+ f"filename: {file_name}, start_time: {start_time}, total_size: {total_size}, "
183
+ f"progress: {progress}, show_progress_bar: {show_progress_bar}"
184
+ )
185
+ return progress == 1.0
186
+
187
+
188
+ def _chunk_size_calculator(file_size: int) -> int:
189
+ # S3 has limitation on the num of parts to be uploaded, this helper method recalculate the num of parts
190
+ if file_size > S3_MAX_OBJECT_SIZE:
191
+ # check if we don't exceed the allowed S3 max file size 5 TiB
192
+ raise ValueError(
193
+ f"File size {file_size} exceeds the maximum file size {S3_MAX_OBJECT_SIZE} allowed in S3."
194
+ )
195
+
196
+ # num_parts = math.ceil(file_size / default_chunk_size)
197
+ # if num_parts is greater than the allowed S3_MAX_PARTS, we update our chunk_size, otherwise we use the default one
198
+ calculated_chunk_size = (
199
+ max(math.ceil(file_size / S3_MAX_PARTS), S3_MIN_PART_SIZE)
200
+ if math.ceil(file_size / S3_DEFAULT_CHUNK_SIZE) > S3_MAX_PARTS
201
+ else S3_DEFAULT_CHUNK_SIZE
202
+ )
203
+ if calculated_chunk_size != S3_DEFAULT_CHUNK_SIZE:
204
+ logger.debug(
205
+ f"Setting chunksize to {calculated_chunk_size} instead of the default {S3_DEFAULT_CHUNK_SIZE}."
206
+ )
207
+ return calculated_chunk_size
208
+
209
+
210
+ def percent(seen_so_far: int, size: float) -> float:
211
+ return 1.0 if seen_so_far >= size or size <= 0 else float(seen_so_far / size)
212
+
213
+
214
+ class SnowflakeProgressPercentage:
215
+ """Built-in Progress bar for PUT commands."""
216
+
217
+ def __init__(
218
+ self,
219
+ filename: str,
220
+ filesize: int | float,
221
+ output_stream: IO | None = sys.stdout,
222
+ show_progress_bar: bool | None = True,
223
+ ) -> None:
224
+ last_pound_char = filename.rfind("#")
225
+ if last_pound_char < 0:
226
+ last_pound_char = len(filename)
227
+ self._filename = os.path.basename(filename[0:last_pound_char])
228
+ self._output_stream = output_stream
229
+ self._show_progress_bar = show_progress_bar
230
+ self._size = float(filesize)
231
+ self._seen_so_far = 0
232
+ self._done = False
233
+ self._start_time = time()
234
+ self._lock = threading.Lock()
235
+
236
+ def __call__(self, bytes_amount: int):
237
+ raise NotImplementedError
238
+
239
+
240
+ class SnowflakeS3ProgressPercentage(SnowflakeProgressPercentage):
241
+ def __init__(
242
+ self,
243
+ filename: str,
244
+ filesize: int | float,
245
+ output_stream: IO | None = sys.stdout,
246
+ show_progress_bar: bool | None = True,
247
+ ) -> None:
248
+ super().__init__(
249
+ filename,
250
+ filesize,
251
+ output_stream=output_stream,
252
+ show_progress_bar=show_progress_bar,
253
+ )
254
+
255
+ def __call__(self, bytes_amount: int) -> None:
256
+ with self._lock:
257
+ if self._output_stream:
258
+ self._seen_so_far += bytes_amount
259
+ percentage = percent(self._seen_so_far, self._size)
260
+ if not self._done:
261
+ self._done = _update_progress(
262
+ self._filename,
263
+ self._start_time,
264
+ self._size,
265
+ percentage,
266
+ output_stream=self._output_stream,
267
+ show_progress_bar=self._show_progress_bar,
268
+ )
269
+
270
+
271
+ class SnowflakeAzureProgressPercentage(SnowflakeProgressPercentage):
272
+ def __init__(
273
+ self,
274
+ filename: str,
275
+ filesize: int | float,
276
+ output_stream: IO | None = sys.stdout,
277
+ show_progress_bar: bool | None = True,
278
+ ) -> None:
279
+ super().__init__(
280
+ filename,
281
+ filesize,
282
+ output_stream=output_stream,
283
+ show_progress_bar=show_progress_bar,
284
+ )
285
+
286
+ def __call__(self, current: int) -> None:
287
+ with self._lock:
288
+ if self._output_stream:
289
+ self._seen_so_far = current
290
+ percentage = percent(self._seen_so_far, self._size)
291
+ if not self._done:
292
+ self._done = _update_progress(
293
+ self._filename,
294
+ self._start_time,
295
+ self._size,
296
+ percentage,
297
+ output_stream=self._output_stream,
298
+ show_progress_bar=self._show_progress_bar,
299
+ )
300
+
301
+
302
+ class StorageCredential:
303
+ def __init__(
304
+ self,
305
+ credentials: dict[str, Any],
306
+ connection: SnowflakeConnection,
307
+ command: str,
308
+ ) -> None:
309
+ self.creds = credentials
310
+ self.timestamp = time()
311
+ self.lock = threading.Lock()
312
+ self.connection = connection
313
+ self._command = command
314
+
315
+ def update(self, cur_timestamp) -> None:
316
+ with self.lock:
317
+ if cur_timestamp < self.timestamp:
318
+ logger.debug(
319
+ "Omitting renewal of storage token, as it already happened."
320
+ )
321
+ return
322
+ logger.debug("Renewing expired storage token.")
323
+ ret = self.connection.cursor()._execute_helper(self._command)
324
+ self.creds = ret["data"]["stageInfo"]["creds"]
325
+ self.timestamp = time()
326
+
327
+
328
+ @dataclass
329
+ class TransferMetadata:
330
+ num_files_started: int = 0
331
+ num_files_completed: int = 0
332
+ chunks_in_queue: int = 0
333
+
334
+
335
+ class SnowflakeFileTransferAgent:
336
+ """Snowflake File Transfer Agent provides cloud provider independent implementation for putting/getting files."""
337
+
338
+ def __init__(
339
+ self,
340
+ cursor: SnowflakeCursor,
341
+ command: str,
342
+ ret: dict[str, Any],
343
+ put_callback: type[SnowflakeProgressPercentage] | None = None,
344
+ put_azure_callback: type[SnowflakeProgressPercentage] | None = None,
345
+ put_callback_output_stream: IO[str] = sys.stdout,
346
+ get_callback: type[SnowflakeProgressPercentage] | None = None,
347
+ get_azure_callback: type[SnowflakeProgressPercentage] | None = None,
348
+ get_callback_output_stream: IO[str] = sys.stdout,
349
+ show_progress_bar: bool = True,
350
+ raise_put_get_error: bool = True,
351
+ force_put_overwrite: bool = True,
352
+ skip_upload_on_content_match: bool = False,
353
+ multipart_threshold: int | None = None,
354
+ source_from_stream: IO[bytes] | None = None,
355
+ use_s3_regional_url: bool = False,
356
+ iobound_tpe_limit: int | None = None,
357
+ unsafe_file_write: bool = False,
358
+ ) -> None:
359
+ self._cursor = cursor
360
+ self._command = command
361
+ self._ret = ret
362
+ self._put_callback = put_callback
363
+ self._put_azure_callback = (
364
+ put_azure_callback if put_azure_callback else put_callback
365
+ )
366
+ self._put_callback_output_stream = put_callback_output_stream
367
+ self._get_callback = get_callback
368
+ self._get_azure_callback = (
369
+ get_azure_callback if get_azure_callback else get_callback
370
+ )
371
+ self._get_callback_output_stream = get_callback_output_stream
372
+ # when we have not checked whether we should use accelerate, this boolean is None
373
+ # _use_accelerate_endpoint in SnowflakeFileTransferAgent could be passed to each SnowflakeS3RestClient
374
+ # so we could avoid check accelerate configuration for each S3 client created for each file meta.
375
+ self._use_accelerate_endpoint: bool | None = None
376
+ self._raise_put_get_error = raise_put_get_error
377
+ self._show_progress_bar = show_progress_bar
378
+ self._force_put_overwrite = force_put_overwrite
379
+ self._skip_upload_on_content_match = skip_upload_on_content_match
380
+ self._source_from_stream = source_from_stream
381
+ # The list of self-sufficient file metas that are sent to
382
+ # remote storage clients to get operated on.
383
+ self._file_metadata: list[SnowflakeFileMeta] = []
384
+ self._results: list[SnowflakeFileMeta] = []
385
+ self._multipart_threshold = multipart_threshold or 67108864 # Historical value
386
+ self._use_s3_regional_url = use_s3_regional_url
387
+ self._credentials: StorageCredential | None = None
388
+ self._iobound_tpe_limit = iobound_tpe_limit
389
+ self._unsafe_file_write = unsafe_file_write
390
+
391
+ def execute(self) -> None:
392
+ self._parse_command()
393
+ self._init_file_metadata()
394
+
395
+ if self._command_type == CMD_TYPE_UPLOAD:
396
+ self._process_file_compression_type()
397
+
398
+ for m in self._file_metadata:
399
+ m.sfagent = self
400
+
401
+ self._transfer_accelerate_config()
402
+
403
+ if self._command_type == CMD_TYPE_DOWNLOAD:
404
+ if not os.path.isdir(self._local_location):
405
+ os.makedirs(self._local_location)
406
+
407
+ if self._stage_location_type == LOCAL_FS:
408
+ if not os.path.isdir(self._stage_info["location"]):
409
+ os.makedirs(self._stage_info["location"])
410
+
411
+ for m in self._file_metadata:
412
+ m.overwrite = self._overwrite
413
+ m.skip_upload_on_content_match = self._skip_upload_on_content_match
414
+ m.sfagent = self
415
+ if self._stage_location_type != LOCAL_FS:
416
+ m.put_callback = self._put_callback
417
+ m.put_azure_callback = self._put_azure_callback
418
+ m.put_callback_output_stream = self._put_callback_output_stream
419
+ m.get_callback = self._get_callback
420
+ m.get_azure_callback = self._get_azure_callback
421
+ m.get_callback_output_stream = self._get_callback_output_stream
422
+ m.show_progress_bar = self._show_progress_bar
423
+
424
+ # multichunk threshold
425
+ m.multipart_threshold = self._multipart_threshold
426
+
427
+ logger.debug(f"parallel=[{self._parallel}]")
428
+ if self._raise_put_get_error and not self._file_metadata:
429
+ Error.errorhandler_wrapper(
430
+ self._cursor.connection,
431
+ self._cursor,
432
+ OperationalError,
433
+ {
434
+ "msg": "While getting file(s) there was an error: "
435
+ "the file does not exist.",
436
+ "errno": ER_FILE_NOT_EXISTS,
437
+ },
438
+ )
439
+ self.transfer(self._file_metadata)
440
+
441
+ # turn enum to string, in order to have backward compatible interface
442
+ for result in self._results:
443
+ result.result_status = result.result_status.value
444
+
445
+ def transfer(self, metas: list[SnowflakeFileMeta]) -> None:
446
+ iobound_tpe_limit = min(len(metas), os.cpu_count())
447
+ logger.debug("Decided IO-bound TPE size: %d", iobound_tpe_limit)
448
+ if self._iobound_tpe_limit is not None:
449
+ logger.debug("IO-bound TPE size is limited to: %d", self._iobound_tpe_limit)
450
+ iobound_tpe_limit = min(iobound_tpe_limit, self._iobound_tpe_limit)
451
+ max_concurrency = self._parallel
452
+ network_tpe = ThreadPoolExecutor(max_concurrency)
453
+ preprocess_tpe = ThreadPoolExecutor(iobound_tpe_limit)
454
+ postprocess_tpe = ThreadPoolExecutor(iobound_tpe_limit)
455
+ logger.debug(f"Chunk ThreadPoolExecutor size: {max_concurrency}")
456
+ cv_main_thread = threading.Condition() # to signal the main thread
457
+ cv_chunk_process = (
458
+ threading.Condition()
459
+ ) # to get more chunks into the chunk_tpe
460
+ files = [self._create_file_transfer_client(m) for m in metas]
461
+ num_total_files = len(metas)
462
+ transfer_metadata = TransferMetadata() # this is protected by cv_chunk_process
463
+ is_upload = self._command_type == CMD_TYPE_UPLOAD
464
+ exception_caught_in_callback: Exception | None = None
465
+ logger.debug(
466
+ "Going to %sload %d files", "up" if is_upload else "down", len(metas)
467
+ )
468
+
469
+ def notify_file_completed() -> None:
470
+ # Increment the number of completed files, then notify the main thread.
471
+ with cv_main_thread:
472
+ transfer_metadata.num_files_completed += 1
473
+ cv_main_thread.notify()
474
+
475
+ def preprocess_done_cb(
476
+ success: bool,
477
+ result: Any,
478
+ file_meta: SnowflakeFileMeta,
479
+ done_client: SnowflakeStorageClient,
480
+ ) -> None:
481
+ if not success:
482
+ logger.debug(f"Failed to prepare {done_client.meta.name}.")
483
+ if is_upload:
484
+ done_client.finish_upload()
485
+ done_client.delete_client_data()
486
+ else:
487
+ done_client.finish_download()
488
+ notify_file_completed()
489
+ elif done_client.meta.result_status == ResultStatus.SKIPPED:
490
+ # this case applies to upload only
491
+ notify_file_completed()
492
+ else:
493
+ logger.debug(f"Finished preparing file {done_client.meta.name}")
494
+ with cv_chunk_process:
495
+ while transfer_metadata.chunks_in_queue > 2 * max_concurrency:
496
+ logger.debug(
497
+ "Chunk queue busy, waiting in file done callback..."
498
+ )
499
+ cv_chunk_process.wait()
500
+ for _chunk_id in range(done_client.num_of_chunks):
501
+ _callback = partial(
502
+ transfer_done_cb,
503
+ done_client=done_client,
504
+ chunk_id=_chunk_id,
505
+ )
506
+ if is_upload:
507
+ network_tpe.submit(
508
+ function_and_callback_wrapper,
509
+ # Work fn
510
+ done_client.upload_chunk,
511
+ # Callback fn
512
+ _callback,
513
+ file_meta,
514
+ # Arguments for work fn
515
+ _chunk_id,
516
+ )
517
+ else:
518
+ network_tpe.submit(
519
+ function_and_callback_wrapper,
520
+ # Work fn
521
+ done_client.download_chunk,
522
+ # Callback fn
523
+ _callback,
524
+ file_meta,
525
+ # Arguments for work fn
526
+ _chunk_id,
527
+ )
528
+ transfer_metadata.chunks_in_queue += 1
529
+ cv_chunk_process.notify()
530
+
531
+ def transfer_done_cb(
532
+ success: bool,
533
+ result: Any,
534
+ file_meta: SnowflakeFileMeta,
535
+ done_client: SnowflakeStorageClient,
536
+ chunk_id: int,
537
+ ) -> None:
538
+ # Note: chunk_id is 0 based while num_of_chunks is count
539
+ logger.debug(
540
+ f"Chunk(id: {chunk_id}) {chunk_id+1}/{done_client.num_of_chunks} of file {done_client.meta.name} reached callback"
541
+ )
542
+ with cv_chunk_process:
543
+ transfer_metadata.chunks_in_queue -= 1
544
+ cv_chunk_process.notify()
545
+
546
+ with done_client.lock:
547
+ if not success:
548
+ # TODO: Cancel other chunks?
549
+ done_client.failed_transfers += 1
550
+ logger.debug(
551
+ f"Chunk {chunk_id} of file {done_client.meta.name} failed to transfer for unexpected exception {result}"
552
+ )
553
+ else:
554
+ done_client.successful_transfers += 1
555
+ logger.debug(
556
+ f"Chunk progress: {done_client.meta.name}: completed: {done_client.successful_transfers} failed: {done_client.failed_transfers} total: {done_client.num_of_chunks}"
557
+ )
558
+ if (
559
+ done_client.successful_transfers + done_client.failed_transfers
560
+ == done_client.num_of_chunks
561
+ ):
562
+ if is_upload:
563
+ done_client.finish_upload()
564
+ done_client.delete_client_data()
565
+ notify_file_completed()
566
+ else:
567
+ postprocess_tpe.submit(
568
+ function_and_callback_wrapper,
569
+ # Work fn
570
+ done_client.finish_download,
571
+ # Callback fn
572
+ partial(postprocess_done_cb, done_client=done_client),
573
+ transfer_metadata,
574
+ )
575
+ logger.debug(
576
+ f"submitting {done_client.meta.name} to done_postprocess"
577
+ )
578
+
579
+ def postprocess_done_cb(
580
+ success: bool,
581
+ result: Any,
582
+ file_meta: SnowflakeFileMeta,
583
+ done_client: SnowflakeStorageClient,
584
+ ) -> None:
585
+ logger.debug(f"File {done_client.meta.name} reached postprocess callback")
586
+
587
+ with done_client.lock:
588
+ if not success:
589
+ done_client.failed_transfers += 1
590
+ logger.debug(
591
+ f"File {done_client.meta.name} failed to transfer for unexpected exception {result}"
592
+ )
593
+ # Whether there was an exception or not, we're done the file.
594
+ notify_file_completed()
595
+
596
+ _T = TypeVar("_T")
597
+
598
+ def function_and_callback_wrapper(
599
+ work: Callable[..., _T],
600
+ _callback: Callable[[bool, _T | Exception, SnowflakeFileMeta], None],
601
+ file_meta: SnowflakeFileMeta,
602
+ *args: Any,
603
+ **kwargs: Any,
604
+ ) -> None:
605
+ """This wrapper makes sure that callbacks are called from the TPEs.
606
+
607
+ If the main thread adds a callback to a future that has already been
608
+ fulfilled then the callback is executed by the main thread. This can
609
+ lead to unexpected slowdowns and behavior.
610
+ """
611
+ try:
612
+ result: tuple[bool, _T | Exception] = (
613
+ True,
614
+ work(*args, **kwargs),
615
+ )
616
+ except Exception as e:
617
+ logger.error(f"An exception was raised in {repr(work)}", exc_info=True)
618
+ file_meta.error_details = e
619
+ result = (False, e)
620
+ try:
621
+ _callback(*result, file_meta)
622
+ except Exception as e:
623
+ # TODO: if an exception happens in a callback, the exception will not
624
+ # propagate to the main thread. We need to save these Exceptions
625
+ # somewhere and then re-raise by the main thread. For now let's log
626
+ # this exception, but for a long term solution see my
627
+ # TODO comment for SnowflakeFileMeta
628
+ with cv_main_thread:
629
+ nonlocal exception_caught_in_callback
630
+ exception_caught_in_callback = e
631
+ cv_main_thread.notify()
632
+ if not result[0]:
633
+ # Re-raising the exception from the work function, it would already
634
+ # be logged at this point
635
+ logger.error(
636
+ f"An exception was raised in {repr(callback)}", exc_info=True
637
+ )
638
+
639
+ for file_client in files:
640
+ callback = partial(preprocess_done_cb, done_client=file_client)
641
+ if is_upload:
642
+ preprocess_tpe.submit(
643
+ function_and_callback_wrapper,
644
+ # Work fn
645
+ file_client.prepare_upload,
646
+ # Callback fn
647
+ callback,
648
+ file_client.meta,
649
+ )
650
+ else:
651
+ preprocess_tpe.submit(
652
+ function_and_callback_wrapper,
653
+ # Work fn
654
+ file_client.prepare_download,
655
+ # Callback fn
656
+ callback,
657
+ file_client.meta,
658
+ )
659
+ transfer_metadata.num_files_started += 1 # TODO: do we need this?
660
+
661
+ with cv_main_thread:
662
+ while transfer_metadata.num_files_completed < num_total_files:
663
+ cv_main_thread.wait()
664
+ if exception_caught_in_callback is not None:
665
+ raise exception_caught_in_callback
666
+
667
+ self._results = metas
668
+
669
+ def _create_file_transfer_client(
670
+ self, meta: SnowflakeFileMeta
671
+ ) -> SnowflakeStorageClient:
672
+ if self._stage_location_type == LOCAL_FS:
673
+ return SnowflakeLocalStorageClient(
674
+ meta,
675
+ self._stage_info,
676
+ 4 * megabyte,
677
+ unsafe_file_write=self._unsafe_file_write,
678
+ )
679
+ elif self._stage_location_type == AZURE_FS:
680
+ return SnowflakeAzureRestClient(
681
+ meta,
682
+ self._credentials,
683
+ AZURE_CHUNK_SIZE,
684
+ self._stage_info,
685
+ unsafe_file_write=self._unsafe_file_write,
686
+ )
687
+ elif self._stage_location_type == S3_FS:
688
+ return SnowflakeS3RestClient(
689
+ meta,
690
+ self._credentials,
691
+ self._stage_info,
692
+ _chunk_size_calculator(meta.src_file_size),
693
+ use_accelerate_endpoint=self._use_accelerate_endpoint,
694
+ use_s3_regional_url=self._use_s3_regional_url,
695
+ unsafe_file_write=self._unsafe_file_write,
696
+ )
697
+ elif self._stage_location_type == GCS_FS:
698
+ return SnowflakeGCSRestClient(
699
+ meta,
700
+ self._credentials,
701
+ self._stage_info,
702
+ self._cursor._connection,
703
+ self._command,
704
+ unsafe_file_write=self._unsafe_file_write,
705
+ )
706
+ raise Exception(f"{self._stage_location_type} is an unknown stage type")
707
+
708
+ def _transfer_accelerate_config(self) -> None:
709
+ if self._stage_location_type == S3_FS and self._file_metadata:
710
+ client = self._create_file_transfer_client(self._file_metadata[0])
711
+ self._use_accelerate_endpoint = client.transfer_accelerate_config()
712
+
713
+ def result(self) -> dict[str, Any]:
714
+ converter_class = self._cursor._connection.converter_class
715
+ rowset = []
716
+ if self._command_type == CMD_TYPE_UPLOAD:
717
+ if hasattr(self, "_results"):
718
+ for meta in self._results:
719
+ if meta.src_compression_type is not None:
720
+ src_compression_type = meta.src_compression_type.name
721
+ else:
722
+ src_compression_type = "NONE"
723
+
724
+ if meta.dst_compression_type is not None:
725
+ dst_compression_type = meta.dst_compression_type.name
726
+ else:
727
+ dst_compression_type = "NONE"
728
+
729
+ error_details: str = (
730
+ repr(meta.error_details)
731
+ if meta.error_details is not None
732
+ else ""
733
+ )
734
+
735
+ src_file_size = (
736
+ meta.src_file_size
737
+ if converter_class != SnowflakeConverterSnowSQL
738
+ else str(meta.src_file_size)
739
+ )
740
+
741
+ dst_file_size = (
742
+ meta.dst_file_size
743
+ if converter_class != SnowflakeConverterSnowSQL
744
+ else str(meta.dst_file_size)
745
+ )
746
+
747
+ logger.debug(
748
+ f"raise_put_get_error: {self._raise_put_get_error}, "
749
+ f"{meta.result_status}, {type(meta.result_status)}, "
750
+ f"{ResultStatus.ERROR}, {type(ResultStatus.ERROR)}",
751
+ )
752
+ if self._raise_put_get_error and error_details:
753
+ Error.errorhandler_wrapper(
754
+ self._cursor.connection,
755
+ self._cursor,
756
+ OperationalError,
757
+ {
758
+ "msg": "While putting file(s) there was an error: "
759
+ f"'{error_details}', this might be caused by "
760
+ f"your access to the blob storage provider, "
761
+ f"or by Snowflake.",
762
+ "errno": ER_FAILED_TO_UPLOAD_TO_STAGE,
763
+ },
764
+ )
765
+ rowset.append(
766
+ [
767
+ meta.name,
768
+ meta.dst_file_name,
769
+ src_file_size,
770
+ dst_file_size,
771
+ src_compression_type,
772
+ dst_compression_type,
773
+ meta.result_status,
774
+ error_details,
775
+ ]
776
+ )
777
+ return {
778
+ "rowtype": [
779
+ result_text_column_desc("source"),
780
+ result_text_column_desc("target"),
781
+ result_fixed_column_desc("source_size"),
782
+ result_fixed_column_desc("target_size"),
783
+ result_text_column_desc("source_compression"),
784
+ result_text_column_desc("target_compression"),
785
+ result_text_column_desc("status"),
786
+ result_text_column_desc("message"),
787
+ ],
788
+ "rowset": sorted(rowset),
789
+ }
790
+ else: # DOWNLOAD
791
+ if hasattr(self, "_results"):
792
+ for meta in self._results:
793
+ dst_file_size = (
794
+ meta.dst_file_size
795
+ if converter_class != SnowflakeConverterSnowSQL
796
+ else str(meta.dst_file_size)
797
+ )
798
+
799
+ error_details: str = (
800
+ repr(meta.error_details)
801
+ if meta.error_details is not None
802
+ else ""
803
+ )
804
+
805
+ if self._raise_put_get_error and error_details:
806
+ Error.errorhandler_wrapper(
807
+ self._cursor.connection,
808
+ self._cursor,
809
+ OperationalError,
810
+ {
811
+ "msg": "While getting file(s) there was an error: "
812
+ f"'{error_details}', this might be caused by "
813
+ f"your access to the blob storage provider, "
814
+ f"or by Snowflake.",
815
+ "errno": ER_FAILED_TO_DOWNLOAD_FROM_STAGE,
816
+ },
817
+ )
818
+
819
+ rowset.append(
820
+ [
821
+ meta.dst_file_name,
822
+ dst_file_size,
823
+ meta.result_status,
824
+ error_details,
825
+ ]
826
+ )
827
+ return {
828
+ "rowtype": [
829
+ result_text_column_desc("file"),
830
+ result_fixed_column_desc("size"),
831
+ result_text_column_desc("status"),
832
+ result_text_column_desc("message"),
833
+ ],
834
+ "rowset": sorted(rowset),
835
+ }
836
+
837
+ def _expand_filenames(self, locations: list[str]) -> list[str]:
838
+ canonical_locations = []
839
+ for file_name in locations:
840
+ if self._command_type == CMD_TYPE_UPLOAD:
841
+ file_name = os.path.expanduser(file_name)
842
+ if (
843
+ IS_WINDOWS
844
+ and len(file_name) > 2
845
+ and file_name[0] == "/"
846
+ and file_name[2] == ":"
847
+ ):
848
+ # Since python 3.13 os.path.isabs returns different values for URI or paths starting with a '/' etc. on Windows (https://github.com/python/cpython/issues/125283)
849
+ # Windows path: /C:/data/file1.txt is not treated as absolute - could be prefixed with another Windows driver's letter and colon.
850
+ file_name = file_name[1:]
851
+ if not os.path.isabs(file_name):
852
+ file_name = os.path.abspath(file_name)
853
+ files = glob.glob(file_name)
854
+ canonical_locations += files
855
+ else:
856
+ canonical_locations.append(file_name)
857
+
858
+ return canonical_locations
859
+
860
+ def _init_encryption_material(self) -> None:
861
+ self._encryption_material = []
862
+ if self._ret["data"].get("encryptionMaterial") is not None:
863
+ root_node = self._ret["data"]["encryptionMaterial"]
864
+ logger.debug(self._command_type)
865
+
866
+ if self._command_type == CMD_TYPE_UPLOAD:
867
+ self._encryption_material.append(
868
+ SnowflakeFileEncryptionMaterial(
869
+ query_stage_master_key=root_node["queryStageMasterKey"],
870
+ query_id=root_node["queryId"],
871
+ smk_id=root_node["smkId"],
872
+ )
873
+ )
874
+ else:
875
+ for elem in root_node:
876
+ if elem is not None:
877
+ self._encryption_material.append(
878
+ SnowflakeFileEncryptionMaterial(
879
+ query_stage_master_key=elem["queryStageMasterKey"],
880
+ query_id=elem["queryId"],
881
+ smk_id=elem["smkId"],
882
+ )
883
+ )
884
+
885
+ def _parse_command(self) -> None:
886
+ if "data" not in self._ret:
887
+ Error.errorhandler_wrapper(
888
+ self._cursor.connection,
889
+ self._cursor,
890
+ DatabaseError,
891
+ {
892
+ "msg": "Failed to parse server's response",
893
+ "errno": ER_INVALID_STAGE_LOCATION,
894
+ },
895
+ )
896
+
897
+ response = self._ret["data"]
898
+
899
+ self._command_type = response["command"]
900
+
901
+ self._init_encryption_material()
902
+
903
+ if not isinstance(response.get("src_locations"), list):
904
+ Error.errorhandler_wrapper(
905
+ self._cursor.connection,
906
+ self._cursor,
907
+ DatabaseError,
908
+ {
909
+ "msg": "Failed to parse the location",
910
+ "errno": ER_INVALID_STAGE_LOCATION,
911
+ },
912
+ )
913
+
914
+ self._src_locations = response["src_locations"]
915
+
916
+ self._parallel = response.get("parallel", 1)
917
+ self._overwrite = self._force_put_overwrite or response.get("overwrite", False)
918
+ self._stage_location_type = response["stageInfo"]["locationType"].upper()
919
+
920
+ if self._stage_location_type not in VALID_STORAGE:
921
+ Error.errorhandler_wrapper(
922
+ self._cursor.connection,
923
+ self._cursor,
924
+ OperationalError,
925
+ {
926
+ "msg": f"Destination location type is not valid: {self._stage_location_type}",
927
+ "errno": ER_INVALID_STAGE_FS,
928
+ },
929
+ )
930
+
931
+ self._stage_location = response["stageInfo"]["location"]
932
+ self._stage_info = response["stageInfo"]
933
+ self._credentials = StorageCredential(
934
+ self._stage_info["creds"], self._cursor.connection, self._command
935
+ )
936
+ self._presigned_urls = self._ret["data"].get("presignedUrls")
937
+
938
+ if self._command_type == CMD_TYPE_UPLOAD:
939
+ if self._source_from_stream:
940
+ self._src_files = self._src_locations
941
+ else:
942
+ self._src_files = list(self._expand_filenames(self._src_locations))
943
+ self._auto_compress = (
944
+ "autoCompress" not in response or response["autoCompress"]
945
+ )
946
+ self._source_compression = (
947
+ response["sourceCompression"].lower()
948
+ if "sourceCompression" in response
949
+ else ""
950
+ )
951
+ else:
952
+ self._src_files = list(self._src_locations)
953
+ self._src_file_to_encryption_material = {}
954
+ if len(response["src_locations"]) == len(self._encryption_material):
955
+ for idx, src_file in enumerate(self._src_files):
956
+ logger.debug(src_file)
957
+ self._src_file_to_encryption_material[src_file] = (
958
+ self._encryption_material[idx]
959
+ )
960
+ elif len(self._encryption_material) != 0:
961
+ # some encryption material exists. Zero means no encryption
962
+ Error.errorhandler_wrapper(
963
+ self._cursor.connection,
964
+ self._cursor,
965
+ InternalError,
966
+ {
967
+ "msg": (
968
+ "The number of downloading files doesn't match "
969
+ f"the encryption materials: files={len(response['src_locations'])}, "
970
+ f"encmat={len(self._encryption_material)}"
971
+ ),
972
+ "errno": ER_INTERNAL_NOT_MATCH_ENCRYPT_MATERIAL,
973
+ },
974
+ )
975
+
976
+ self._local_location = os.path.expanduser(response["localLocation"])
977
+ if not os.path.isdir(self._local_location):
978
+ # NOTE: isdir follows the symlink
979
+ Error.errorhandler_wrapper(
980
+ self._cursor.connection,
981
+ self._cursor,
982
+ ProgrammingError,
983
+ {
984
+ "msg": f"The local path is not a directory: {self._local_location}",
985
+ "errno": ER_LOCAL_PATH_NOT_DIRECTORY,
986
+ },
987
+ )
988
+
989
+ def _init_file_metadata(self) -> None:
990
+ logger.debug(f"command type: {self._command_type}")
991
+
992
+ if self._command_type == CMD_TYPE_UPLOAD:
993
+ if not self._src_files:
994
+ file_name = (
995
+ self._ret["data"]["src_locations"]
996
+ if "data" in self._ret and "src_locations" in self._ret["data"]
997
+ else "None"
998
+ )
999
+ Error.errorhandler_wrapper(
1000
+ self._cursor.connection,
1001
+ self._cursor,
1002
+ ProgrammingError,
1003
+ {
1004
+ "msg": f"File doesn't exist: {file_name}",
1005
+ "errno": ER_FILE_NOT_EXISTS,
1006
+ },
1007
+ )
1008
+ if self._source_from_stream:
1009
+ self._file_metadata.append(
1010
+ SnowflakeFileMeta(
1011
+ name=os.path.basename(self._src_files[0]),
1012
+ src_file_name=self._src_files[0],
1013
+ intermediate_stream=self._source_from_stream,
1014
+ src_file_size=self._source_from_stream.seek(0, os.SEEK_END),
1015
+ stage_location_type=self._stage_location_type,
1016
+ encryption_material=(
1017
+ self._encryption_material[0]
1018
+ if len(self._encryption_material) > 0
1019
+ else None
1020
+ ),
1021
+ )
1022
+ )
1023
+ self._source_from_stream.seek(0)
1024
+ else:
1025
+ for file_name in self._src_files:
1026
+ if not os.path.exists(file_name):
1027
+ Error.errorhandler_wrapper(
1028
+ self._cursor.connection,
1029
+ self._cursor,
1030
+ ProgrammingError,
1031
+ {
1032
+ "msg": f"File doesn't exist: {file_name}",
1033
+ "errno": ER_FILE_NOT_EXISTS,
1034
+ },
1035
+ )
1036
+ elif os.path.isdir(file_name):
1037
+ Error.errorhandler_wrapper(
1038
+ self._cursor.connection,
1039
+ self._cursor,
1040
+ ProgrammingError,
1041
+ {
1042
+ "msg": f"Not a file but a directory: {file_name}",
1043
+ "errno": ER_FILE_NOT_EXISTS,
1044
+ },
1045
+ )
1046
+ statinfo = os.stat(file_name)
1047
+ self._file_metadata.append(
1048
+ SnowflakeFileMeta(
1049
+ name=os.path.basename(file_name),
1050
+ src_file_name=file_name,
1051
+ src_file_size=statinfo.st_size,
1052
+ stage_location_type=self._stage_location_type,
1053
+ encryption_material=(
1054
+ self._encryption_material[0]
1055
+ if len(self._encryption_material) > 0
1056
+ else None
1057
+ ),
1058
+ )
1059
+ )
1060
+ elif self._command_type == CMD_TYPE_DOWNLOAD:
1061
+ basename_counts = dict()
1062
+ for idx, file_name in enumerate(self._src_files):
1063
+ if not file_name:
1064
+ continue
1065
+ dst_file_name = (
1066
+ self._strip_stage_prefix_from_dst_file_name_for_download(file_name)
1067
+ )
1068
+ first_path_sep = dst_file_name.find("/")
1069
+ dst_file_name = (
1070
+ dst_file_name[first_path_sep + 1 :]
1071
+ if first_path_sep >= 0
1072
+ else dst_file_name
1073
+ )
1074
+ url = None
1075
+ if self._presigned_urls and idx < len(self._presigned_urls):
1076
+ url = self._presigned_urls[idx]
1077
+
1078
+ basename = os.path.basename(file_name)
1079
+ basename_counts[basename] = basename_counts.get(basename, 0) + 1
1080
+
1081
+ self._file_metadata.append(
1082
+ SnowflakeFileMeta(
1083
+ name=basename,
1084
+ src_file_name=file_name,
1085
+ dst_file_name=dst_file_name,
1086
+ stage_location_type=self._stage_location_type,
1087
+ local_location=self._local_location,
1088
+ presigned_url=url,
1089
+ encryption_material=(
1090
+ self._src_file_to_encryption_material[file_name]
1091
+ if file_name in self._src_file_to_encryption_material
1092
+ else None
1093
+ ),
1094
+ )
1095
+ )
1096
+
1097
+ # TODO: remove this warning once we support directory structure for GET
1098
+ duplicate_basenames = [k for k, v in basename_counts.items() if v > 1]
1099
+ if duplicate_basenames:
1100
+ logger.warning(
1101
+ f"Downloading multiple files with the same name could cause failures. "
1102
+ f"File names with multiple entries: {duplicate_basenames}"
1103
+ )
1104
+
1105
+ def _process_file_compression_type(self) -> None:
1106
+ user_specified_source_compression = None
1107
+ if self._source_compression == "auto_detect":
1108
+ auto_detect = True
1109
+ elif self._source_compression == "none":
1110
+ auto_detect = False
1111
+ else:
1112
+ user_specified_source_compression = lookup_by_mime_sub_type(
1113
+ self._source_compression
1114
+ )
1115
+ if (
1116
+ user_specified_source_compression is None
1117
+ or not user_specified_source_compression.is_supported
1118
+ ):
1119
+ Error.errorhandler_wrapper(
1120
+ self._cursor.connection,
1121
+ self._cursor,
1122
+ ProgrammingError,
1123
+ {
1124
+ "msg": f"Feature is not supported: {user_specified_source_compression}",
1125
+ "errno": ER_COMPRESSION_NOT_SUPPORTED,
1126
+ },
1127
+ )
1128
+
1129
+ auto_detect = False
1130
+
1131
+ for m in self._file_metadata:
1132
+ file_name = m.src_file_name
1133
+
1134
+ current_file_compression_type: CompressionType | None = None
1135
+ if auto_detect:
1136
+ mimetypes.init()
1137
+ _, encoding = mimetypes.guess_type(file_name)
1138
+
1139
+ if encoding is None:
1140
+ test = None
1141
+ if not self._source_from_stream:
1142
+ with open(file_name, "rb") as f:
1143
+ test = f.read(4)
1144
+ else:
1145
+ test = self._source_from_stream.read(4)
1146
+ self._source_from_stream.seek(0)
1147
+ if file_name.endswith(".br"):
1148
+ encoding = "br"
1149
+ elif test and test[:3] == b"ORC":
1150
+ encoding = "orc"
1151
+ elif test and test == b"PAR1":
1152
+ encoding = "parquet"
1153
+ elif test and (int(binascii.hexlify(test), 16) == 0x28B52FFD):
1154
+ encoding = "zstd"
1155
+
1156
+ if encoding is not None:
1157
+ logger.debug(
1158
+ f"detected the encoding {encoding}: file={file_name}",
1159
+ )
1160
+ current_file_compression_type = lookup_by_mime_sub_type(encoding)
1161
+ else:
1162
+ logger.debug(f"no file encoding was detected: file={file_name}")
1163
+
1164
+ if (
1165
+ current_file_compression_type is not None
1166
+ and not current_file_compression_type.is_supported
1167
+ ):
1168
+ Error.errorhandler_wrapper(
1169
+ self._cursor.connection,
1170
+ self._cursor,
1171
+ ProgrammingError,
1172
+ {
1173
+ "msg": f"Feature is not supported: {current_file_compression_type}",
1174
+ "errno": ER_COMPRESSION_NOT_SUPPORTED,
1175
+ },
1176
+ )
1177
+ else:
1178
+ current_file_compression_type = user_specified_source_compression
1179
+
1180
+ if current_file_compression_type is not None:
1181
+ m.src_compression_type = current_file_compression_type
1182
+ if current_file_compression_type.is_supported:
1183
+ m.dst_compression_type = current_file_compression_type
1184
+ m.require_compress = False
1185
+ m.dst_file_name = m.name
1186
+ else:
1187
+ Error.errorhandler_wrapper(
1188
+ self._cursor.connection,
1189
+ self._cursor,
1190
+ ProgrammingError,
1191
+ {
1192
+ "msg": f"Feature is not supported: {current_file_compression_type}",
1193
+ "errno": ER_COMPRESSION_NOT_SUPPORTED,
1194
+ },
1195
+ )
1196
+ else:
1197
+ # src is not compressed but the destination want to be
1198
+ # compressed unless the users disable it
1199
+ m.require_compress = self._auto_compress
1200
+ m.src_compression_type = None
1201
+ if self._auto_compress:
1202
+ m.dst_file_name = m.name + CompressionTypes["GZIP"].file_extension
1203
+ m.dst_compression_type = CompressionTypes["GZIP"]
1204
+ else:
1205
+ m.dst_file_name = m.name
1206
+ m.dst_compression_type = None
1207
+
1208
+ def _strip_stage_prefix_from_dst_file_name_for_download(self, dst_file_name):
1209
+ """Strips the stage prefix from dst_file_name for download.
1210
+
1211
+ Note that this is no-op in most cases, and therefore we return as is.
1212
+ But for some workloads they will monkeypatch this method to add their
1213
+ stripping logic.
1214
+ """
1215
+ return dst_file_name