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,96 @@
1
+ #!/usr/bin/env python
2
+ # Python Db API v2
3
+ #
4
+ from __future__ import annotations
5
+
6
+ from functools import wraps
7
+
8
+ apilevel = "2.0"
9
+ threadsafety = 2
10
+ paramstyle = "pyformat"
11
+
12
+ import logging
13
+ from logging import NullHandler
14
+
15
+ from snowflake.connector.externals_utils.externals_setup import setup_external_libraries
16
+
17
+ from .connection import SnowflakeConnection
18
+ from .cursor import DictCursor
19
+ from .dbapi import (
20
+ BINARY,
21
+ DATETIME,
22
+ NUMBER,
23
+ ROWID,
24
+ STRING,
25
+ Binary,
26
+ Date,
27
+ DateFromTicks,
28
+ Time,
29
+ TimeFromTicks,
30
+ Timestamp,
31
+ TimestampFromTicks,
32
+ )
33
+ from .errors import (
34
+ DatabaseError,
35
+ DataError,
36
+ Error,
37
+ IntegrityError,
38
+ InterfaceError,
39
+ InternalError,
40
+ NotSupportedError,
41
+ OperationalError,
42
+ ProgrammingError,
43
+ _Warning,
44
+ )
45
+ from .log_configuration import EasyLoggingConfigPython
46
+ from .version import VERSION
47
+
48
+ logging.getLogger(__name__).addHandler(NullHandler())
49
+ setup_external_libraries()
50
+
51
+
52
+ @wraps(SnowflakeConnection.__init__)
53
+ def Connect(**kwargs) -> SnowflakeConnection:
54
+ return SnowflakeConnection(**kwargs)
55
+
56
+
57
+ connect = Connect
58
+
59
+ SNOWFLAKE_CONNECTOR_VERSION = ".".join(str(v) for v in VERSION[0:3])
60
+ __version__ = SNOWFLAKE_CONNECTOR_VERSION
61
+
62
+ __all__ = [
63
+ "SnowflakeConnection",
64
+ # Error handling
65
+ "Error",
66
+ "_Warning",
67
+ "InterfaceError",
68
+ "DatabaseError",
69
+ "NotSupportedError",
70
+ "DataError",
71
+ "IntegrityError",
72
+ "ProgrammingError",
73
+ "OperationalError",
74
+ "InternalError",
75
+ # Extended cursor
76
+ "DictCursor",
77
+ # DBAPI PEP 249 required exports
78
+ "connect",
79
+ "apilevel",
80
+ "threadsafety",
81
+ "paramstyle",
82
+ "Date",
83
+ "Time",
84
+ "Timestamp",
85
+ "Binary",
86
+ "DateFromTicks",
87
+ "TimeFromTicks",
88
+ "TimestampFromTicks",
89
+ "STRING",
90
+ "BINARY",
91
+ "NUMBER",
92
+ "DATETIME",
93
+ "ROWID",
94
+ # Extended data type (experimental)
95
+ "EasyLoggingConfigPython",
96
+ ]
@@ -0,0 +1,276 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import total_ordering
4
+ from hashlib import md5
5
+ from logging import getLogger
6
+ from threading import Lock
7
+ from typing import Any, Iterable
8
+
9
+ from sortedcontainers import SortedSet
10
+
11
+ logger = getLogger(__name__)
12
+
13
+
14
+ @total_ordering
15
+ class QueryContextElement:
16
+ def __init__(
17
+ self, id: int, read_timestamp: int, priority: int, context: str
18
+ ) -> None:
19
+ # entry with id = 0 is the main entry
20
+ self.id = id
21
+ self.read_timestamp = read_timestamp
22
+ # priority values are 0..N with 0 being the highest priority
23
+ self.priority = priority
24
+ # OpaqueContext field will be base64 encoded in GS, but it is opaque to client side. Client side should not do decoding/encoding and just store the raw data.
25
+ self.context = context
26
+
27
+ def __eq__(self, other: object) -> bool:
28
+ if not isinstance(other, QueryContextElement):
29
+ return False
30
+ return (
31
+ self.id == other.id
32
+ and self.read_timestamp == other.read_timestamp
33
+ and self.priority == other.priority
34
+ and self.context == other.context
35
+ )
36
+
37
+ def __lt__(self, other: Any) -> bool:
38
+ if not isinstance(other, QueryContextElement):
39
+ raise TypeError(
40
+ f"cannot compare QueryContextElement with object of type {type(other)}"
41
+ )
42
+ return self.priority < other.priority
43
+
44
+ def __hash__(self) -> int:
45
+ _hash = 31
46
+
47
+ _hash = _hash * 31 + self.id
48
+ _hash += (_hash * 31) + self.read_timestamp
49
+ _hash += (_hash * 31) + self.priority
50
+ if self.context:
51
+ _hash += (_hash * 31) + int.from_bytes(
52
+ md5(self.context.encode("utf-8")).digest(), "big"
53
+ )
54
+ return _hash
55
+
56
+ def __str__(self) -> str:
57
+ return f"({self.id}, {self.read_timestamp}, {self.priority})"
58
+
59
+
60
+ class QueryContextCache:
61
+ def __init__(self, capacity: int) -> None:
62
+ self.capacity = capacity
63
+ self._id_map: dict[int, QueryContextElement] = {}
64
+ self._priority_map: dict[int, QueryContextElement] = {}
65
+ self._intermediate_priority_map: dict[int, QueryContextElement] = {}
66
+
67
+ # stores elements sorted by priority. Element with
68
+ # least priority value has the highest priority
69
+ self._tree_set: set[QueryContextElement] = SortedSet()
70
+ self._lock = Lock()
71
+ self._data: str = None
72
+
73
+ def _add_qce(self, qce: QueryContextElement) -> None:
74
+ """Adds qce element in tree_set, id_map and intermediate_priority_map.
75
+ We still need to add _sync_priority_map after all the new qce have been merged
76
+ into the cache.
77
+ """
78
+ self._tree_set.add(qce)
79
+ self._id_map[qce.id] = qce
80
+ self._intermediate_priority_map[qce.priority] = qce
81
+
82
+ def _remove_qce(self, qce: QueryContextElement) -> None:
83
+ self._id_map.pop(qce.id)
84
+ self._priority_map.pop(qce.priority)
85
+ self._tree_set.remove(qce)
86
+
87
+ def _replace_qce(
88
+ self, old_qce: QueryContextElement, new_qce: QueryContextElement
89
+ ) -> None:
90
+ """This is just a convenience function to call a remove and add operation back-to-back"""
91
+ self._remove_qce(old_qce)
92
+ self._add_qce(new_qce)
93
+
94
+ def _sync_priority_map(self):
95
+ """
96
+ Sync the _intermediate_priority_map with the _priority_map at the end of the current round of inserts.
97
+ """
98
+ logger.debug(
99
+ f"sync_priority_map called priority_map size = {len(self._priority_map)}, new_priority_map size = {len(self._intermediate_priority_map)}"
100
+ )
101
+
102
+ self._priority_map.update(self._intermediate_priority_map)
103
+ # Clear the _intermediate_priority_map for the next round of QCC insert (a round consists of multiple entries)
104
+ self._intermediate_priority_map.clear()
105
+
106
+ def insert(self, id: int, read_timestamp: int, priority: int, context: str) -> None:
107
+ if id in self._id_map:
108
+ qce = self._id_map[id]
109
+ if (read_timestamp > qce.read_timestamp) or (
110
+ read_timestamp == qce.read_timestamp and priority != qce.priority
111
+ ):
112
+ # when id if found in cache and we are operating on a more recent timestamp. We do not update in-place here.
113
+ new_qce = QueryContextElement(id, read_timestamp, priority, context)
114
+ self._replace_qce(qce, new_qce)
115
+ else:
116
+ new_qce = QueryContextElement(id, read_timestamp, priority, context)
117
+ if priority in self._priority_map:
118
+ old_qce = self._priority_map[priority]
119
+ self._replace_qce(old_qce, new_qce)
120
+ else:
121
+ self._add_qce(new_qce)
122
+
123
+ def trim_cache(self) -> None:
124
+ logger.debug(
125
+ f"trim_cache() called. treeSet size is {len(self._tree_set)} and cache capacity is {self.capacity}"
126
+ )
127
+
128
+ while len(self) > self.capacity:
129
+ # remove the qce with highest priority value => element with least priority
130
+ qce = self._last()
131
+ self._remove_qce(qce)
132
+
133
+ logger.debug(
134
+ f"trim_cache() returns. treeSet size is {len(self._tree_set)} and cache capacity is {self.capacity}"
135
+ )
136
+
137
+ def clear_cache(self) -> None:
138
+ logger.debug("clear_cache() called")
139
+ self._id_map.clear()
140
+ self._priority_map.clear()
141
+ self._tree_set.clear()
142
+ self._intermediate_priority_map.clear()
143
+
144
+ def _get_elements(self) -> Iterable[QueryContextElement]:
145
+ return self._tree_set
146
+
147
+ def _last(self) -> QueryContextElement:
148
+ return self._tree_set[-1]
149
+
150
+ def serialize_to_dict(self) -> dict:
151
+ with self._lock:
152
+ logger.debug("serialize_to_dict() called")
153
+ self.log_cache_entries()
154
+
155
+ if len(self._tree_set) == 0:
156
+ return {} # we should return an empty dict
157
+
158
+ try:
159
+ data = {
160
+ "entries": [
161
+ {
162
+ "id": qce.id,
163
+ "timestamp": qce.read_timestamp,
164
+ "priority": qce.priority,
165
+ "context": (
166
+ {"base64Data": qce.context}
167
+ if qce.context is not None
168
+ else {}
169
+ ),
170
+ }
171
+ for qce in self._tree_set
172
+ ]
173
+ }
174
+ # Because on GS side, `context` field is an object with `base64Data` string member variable,
175
+ # we should serialize `context` field to an object instead of string directly to stay consistent with GS side.
176
+
177
+ logger.debug(f"serialize_to_dict(): data to send to server {data}")
178
+
179
+ # query context shoule be an object field of the HTTP request body JSON and on GS side. here we should only return a dict
180
+ # and let the outer HTTP request body to convert the entire big dict to a single JSON.
181
+ return data
182
+ except Exception as e:
183
+ logger.debug(f"serialize_to_dict(): Exception {e}")
184
+ return {}
185
+
186
+ def deserialize_json_dict(self, data: dict) -> None:
187
+ with self._lock:
188
+ logger.debug(f"deserialize_json_dict() called: data from server: {data}")
189
+ self.log_cache_entries()
190
+
191
+ if data is None or len(data) == 0:
192
+ self.clear_cache()
193
+ logger.debug("deserialize_json_dict() returns")
194
+ self.log_cache_entries()
195
+ return
196
+
197
+ try:
198
+ # Deserialize the entries. The first entry with priority 0 is the main entry. On python
199
+ # connector side, we save all entries into one list to simplify the logic. When python
200
+ # connector receives HTTP response, the data["queryContext"] field has been converted
201
+ # from JSON to dict type automatically, so for this function we deserialize from python
202
+ # dict directly. Below is an example QueryContext dict.
203
+ # {
204
+ # "entries": [
205
+ # {
206
+ # "id": 0,
207
+ # "read_timestamp": 123456789,
208
+ # "priority": 0,
209
+ # "context": "base64 encoded context"
210
+ # },
211
+ # {
212
+ # "id": 1,
213
+ # "read_timestamp": 123456789,
214
+ # "priority": 1,
215
+ # "context": "base64 encoded context"
216
+ # },
217
+ # {
218
+ # "id": 2,
219
+ # "read_timestamp": 123456789,
220
+ # "priority": 2,
221
+ # "context": "base64 encoded context"
222
+ # }
223
+ # ]
224
+ # }
225
+
226
+ # Deserialize entries
227
+ entries = data.get("entries", list())
228
+ for entry in entries:
229
+ logger.debug(f"deserialize {entry}")
230
+ if not isinstance(entry.get("id"), int):
231
+ logger.debug("id type error")
232
+ raise TypeError(
233
+ f"Invalid type for 'id' field: Expected int, got {type(entry['id'])}"
234
+ )
235
+ if not isinstance(entry.get("timestamp"), int):
236
+ logger.debug("timestamp type error")
237
+ raise TypeError(
238
+ f"Invalid type for 'timestamp' field: Expected int, got {type(entry['timestamp'])}"
239
+ )
240
+ if not isinstance(entry.get("priority"), int):
241
+ logger.debug("priority type error")
242
+ raise TypeError(
243
+ f"Invalid type for 'priority' field: Expected int, got {type(entry['priority'])}"
244
+ )
245
+
246
+ # OpaqueContext field currently is empty from GS side.
247
+ context = entry.get("context", None)
248
+ if context and not isinstance(entry.get("context"), str):
249
+ logger.debug("context type error")
250
+ raise TypeError(
251
+ f"Invalid type for 'context' field: Expected str, got {type(entry['context'])}"
252
+ )
253
+ self.insert(
254
+ entry.get("id"),
255
+ entry.get("timestamp"),
256
+ entry.get("priority"),
257
+ context,
258
+ )
259
+
260
+ # Sync the priority map at the end of for loop insert.
261
+ self._sync_priority_map()
262
+ except Exception as e:
263
+ logger.debug(f"deserialize_json_dict: Exception = {e}")
264
+ # clear cache due to incomplete insert
265
+ self.clear_cache()
266
+
267
+ self.trim_cache()
268
+ logger.debug("deserialize_json_dict() returns")
269
+ self.log_cache_entries()
270
+
271
+ def log_cache_entries(self) -> None:
272
+ for qce in self._tree_set:
273
+ logger.debug(f"Cache Entry: {str(qce)}")
274
+
275
+ def __len__(self) -> int:
276
+ return len(self._tree_set)
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+
5
+ from .constants import FileTransferType
6
+
7
+ COMMENT_START_SQL_RE = re.compile(
8
+ r"""
9
+ ^\s*(?:
10
+ /\*[\w\W]*?\*/
11
+ )""",
12
+ re.VERBOSE,
13
+ )
14
+
15
+ PUT_SQL_RE = re.compile(r"^\s*put", flags=re.IGNORECASE)
16
+ GET_SQL_RE = re.compile(r"^\s*get", flags=re.IGNORECASE)
17
+
18
+
19
+ def remove_starting_comments(sql: str) -> str:
20
+ """Remove all comments from the start of a SQL statement."""
21
+ commentless_sql = sql
22
+ while True:
23
+ start_comment = COMMENT_START_SQL_RE.match(commentless_sql)
24
+ if start_comment is None:
25
+ break
26
+ commentless_sql = commentless_sql[start_comment.end() :]
27
+ return commentless_sql
28
+
29
+
30
+ def get_file_transfer_type(sql: str) -> FileTransferType | None:
31
+ """Decide whether a SQL is a file transfer and return its type.
32
+
33
+ None is returned if the SQL isn't a file transfer so that this function can be
34
+ used in an if-statement.
35
+ """
36
+ commentless_sql = remove_starting_comments(sql)
37
+ if PUT_SQL_RE.match(commentless_sql):
38
+ return FileTransferType.PUT
39
+ elif GET_SQL_RE.match(commentless_sql):
40
+ return FileTransferType.GET
41
+
42
+
43
+ def is_put_statement(sql: str) -> bool:
44
+ return get_file_transfer_type(sql) == FileTransferType.PUT
45
+
46
+
47
+ def is_get_statement(sql: str) -> bool:
48
+ return get_file_transfer_type(sql) == FileTransferType.GET
@@ -0,0 +1,70 @@
1
+ from __future__ import annotations
2
+
3
+ import string
4
+ from enum import Enum
5
+ from random import choice
6
+ from threading import Timer
7
+ from uuid import UUID
8
+
9
+
10
+ class TempObjectType(Enum):
11
+ TABLE = "TABLE"
12
+ VIEW = "VIEW"
13
+ STAGE = "STAGE"
14
+ FUNCTION = "FUNCTION"
15
+ FILE_FORMAT = "FILE_FORMAT"
16
+ QUERY_TAG = "QUERY_TAG"
17
+ COLUMN = "COLUMN"
18
+ PROCEDURE = "PROCEDURE"
19
+ TABLE_FUNCTION = "TABLE_FUNCTION"
20
+ DYNAMIC_TABLE = "DYNAMIC_TABLE"
21
+ AGGREGATE_FUNCTION = "AGGREGATE_FUNCTION"
22
+ CTE = "CTE"
23
+
24
+
25
+ TEMP_OBJECT_NAME_PREFIX = "SNOWPARK_TEMP_"
26
+ ALPHANUMERIC = string.digits + string.ascii_lowercase
27
+ TEMPORARY_STRING = "TEMP"
28
+ SCOPED_TEMPORARY_STRING = "SCOPED TEMPORARY"
29
+ _PYTHON_SNOWPARK_USE_SCOPED_TEMP_OBJECTS_STRING = (
30
+ "PYTHON_SNOWPARK_USE_SCOPED_TEMP_OBJECTS"
31
+ )
32
+
33
+ REQUEST_ID_STATEMENT_PARAM_NAME = "requestId"
34
+
35
+
36
+ def generate_random_alphanumeric(length: int = 10) -> str:
37
+ return "".join(choice(ALPHANUMERIC) for _ in range(length))
38
+
39
+
40
+ def random_name_for_temp_object(object_type: TempObjectType) -> str:
41
+ return f"{TEMP_OBJECT_NAME_PREFIX}{object_type.value}_{generate_random_alphanumeric().upper()}"
42
+
43
+
44
+ def get_temp_type_for_object(use_scoped_temp_objects: bool) -> str:
45
+ return SCOPED_TEMPORARY_STRING if use_scoped_temp_objects else TEMPORARY_STRING
46
+
47
+
48
+ def is_uuid4(str_or_uuid: str | UUID) -> bool:
49
+ """Check whether provided string str is a valid UUID version4."""
50
+ if isinstance(str_or_uuid, UUID):
51
+ return str_or_uuid.version == 4
52
+
53
+ if not isinstance(str_or_uuid, str):
54
+ return False
55
+
56
+ try:
57
+ uuid_str = str(UUID(str_or_uuid, version=4))
58
+ except ValueError:
59
+ return False
60
+ return uuid_str == str_or_uuid
61
+
62
+
63
+ class _TrackedQueryCancellationTimer(Timer):
64
+ def __init__(self, interval, function, args=None, kwargs=None):
65
+ super().__init__(interval, function, args, kwargs)
66
+ self.executed = False
67
+
68
+ def run(self):
69
+ super().run()
70
+ self.executed = True
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env python
2
+ from __future__ import annotations
3
+
4
+ import decimal
5
+ import time
6
+ from datetime import datetime, timedelta, timezone, tzinfo
7
+ from logging import getLogger
8
+ from sys import byteorder
9
+ from typing import TYPE_CHECKING
10
+
11
+ import pytz
12
+ from pytz import UTC
13
+
14
+ from .constants import PARAMETER_TIMEZONE
15
+ from .converter import _generate_tzinfo_from_tzoffset
16
+
17
+ if TYPE_CHECKING:
18
+ from numpy import datetime64, float64, int64, timedelta64
19
+
20
+
21
+ try:
22
+ import numpy
23
+ except ImportError:
24
+ numpy = None
25
+
26
+
27
+ try:
28
+ import tzlocal
29
+ except ImportError:
30
+ tzlocal = None
31
+
32
+ ZERO_EPOCH = datetime.fromtimestamp(0, timezone.utc).replace(tzinfo=None)
33
+
34
+ logger = getLogger(__name__)
35
+
36
+
37
+ class ArrowConverterContext:
38
+ """Python helper functions for arrow conversions.
39
+
40
+ Windows timestamp functions are necessary because Windows cannot handle -ve timestamps.
41
+ Putting the OS check into the non-windows function would probably take up more CPU cycles then
42
+ just deciding this at compile time.
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ session_parameters: dict[str, str | int | bool] | None = None,
48
+ ) -> None:
49
+ if session_parameters is None:
50
+ session_parameters = {}
51
+ self._timezone = (
52
+ None
53
+ if PARAMETER_TIMEZONE not in session_parameters
54
+ else session_parameters[PARAMETER_TIMEZONE]
55
+ )
56
+
57
+ @property
58
+ def timezone(self) -> str:
59
+ return self._timezone
60
+
61
+ @timezone.setter
62
+ def timezone(self, tz) -> None:
63
+ self._timezone = tz
64
+
65
+ def _get_session_tz(self) -> tzinfo | UTC:
66
+ """Get the session timezone or use the local computer's timezone."""
67
+ try:
68
+ tz = "UTC" if not self.timezone else self.timezone
69
+ return pytz.timezone(tz)
70
+ except pytz.exceptions.UnknownTimeZoneError:
71
+ logger.warning("converting to tzinfo failed")
72
+ if tzlocal is not None:
73
+ return tzlocal.get_localzone()
74
+ else:
75
+ try:
76
+ return datetime.timezone.utc
77
+ except AttributeError:
78
+ return pytz.timezone("UTC")
79
+
80
+ def TIMESTAMP_TZ_to_python(
81
+ self, epoch: int, microseconds: int, tz: int
82
+ ) -> datetime:
83
+ tzinfo = _generate_tzinfo_from_tzoffset(tz - 1440)
84
+ return datetime.fromtimestamp(epoch, tz=tzinfo) + timedelta(
85
+ microseconds=microseconds
86
+ )
87
+
88
+ def TIMESTAMP_TZ_to_python_windows(
89
+ self, epoch: int, microseconds: int, tz: int
90
+ ) -> datetime:
91
+ tzinfo = _generate_tzinfo_from_tzoffset(tz - 1440)
92
+ t = ZERO_EPOCH + timedelta(seconds=epoch, microseconds=microseconds)
93
+ if pytz.utc != tzinfo:
94
+ t += tzinfo.utcoffset(t)
95
+ return t.replace(tzinfo=tzinfo)
96
+
97
+ def TIMESTAMP_NTZ_to_python(self, epoch: int, microseconds: int) -> datetime:
98
+ return datetime.fromtimestamp(epoch, timezone.utc).replace(
99
+ tzinfo=None
100
+ ) + timedelta(microseconds=microseconds)
101
+
102
+ def TIMESTAMP_NTZ_to_python_windows(
103
+ self, epoch: int, microseconds: int
104
+ ) -> datetime:
105
+ return ZERO_EPOCH + timedelta(seconds=epoch, microseconds=microseconds)
106
+
107
+ def TIMESTAMP_LTZ_to_python(self, epoch: int, microseconds: int) -> datetime:
108
+ tzinfo = self._get_session_tz()
109
+ return datetime.fromtimestamp(epoch, tz=tzinfo) + timedelta(
110
+ microseconds=microseconds
111
+ )
112
+
113
+ def TIMESTAMP_LTZ_to_python_windows(
114
+ self, epoch: int, microseconds: int
115
+ ) -> datetime:
116
+ try:
117
+ tzinfo = self._get_session_tz()
118
+ ts = ZERO_EPOCH + timedelta(seconds=epoch, microseconds=microseconds)
119
+ return pytz.utc.localize(ts, is_dst=False).astimezone(tzinfo)
120
+ except OverflowError:
121
+ logger.debug(
122
+ "OverflowError in converting from epoch time to "
123
+ "timestamp_ltz: %s(ms). Falling back to use struct_time."
124
+ )
125
+ return time.localtime(microseconds)
126
+
127
+ def REAL_to_numpy_float64(self, py_double: float) -> float64:
128
+ return numpy.float64(py_double)
129
+
130
+ def FIXED_to_numpy_int64(self, py_long: int) -> int64:
131
+ return numpy.int64(py_long)
132
+
133
+ def FIXED_to_numpy_float64(self, py_long: int, scale: int) -> float64:
134
+ return numpy.float64(decimal.Decimal(py_long).scaleb(-scale))
135
+
136
+ def DATE_to_numpy_datetime64(self, py_days: int) -> datetime64:
137
+ return numpy.datetime64(py_days, "D")
138
+
139
+ def TIMESTAMP_NTZ_ONE_FIELD_to_numpy_datetime64(
140
+ self, value: int, scale: int
141
+ ) -> datetime64:
142
+ nanoseconds = int(decimal.Decimal(value).scaleb(9 - scale))
143
+ return numpy.datetime64(nanoseconds, "ns")
144
+
145
+ def TIMESTAMP_NTZ_TWO_FIELD_to_numpy_datetime64(
146
+ self, epoch: int, fraction: int
147
+ ) -> datetime64:
148
+ nanoseconds = int(decimal.Decimal(epoch).scaleb(9) + decimal.Decimal(fraction))
149
+ return numpy.datetime64(nanoseconds, "ns")
150
+
151
+ def DECIMAL128_to_decimal(self, int128_bytes: bytes, scale: int) -> decimal.Decimal:
152
+ int128 = int.from_bytes(int128_bytes, byteorder=byteorder, signed=True)
153
+ if scale == 0:
154
+ return int128
155
+ digits = [int(digit) for digit in str(int128) if digit != "-"]
156
+ sign = int128 < 0
157
+ return decimal.Decimal((sign, digits, -scale))
158
+
159
+ def DECFLOAT_to_decimal(self, exponent: int, significand: bytes) -> decimal.Decimal:
160
+ # significand is two's complement big endian.
161
+ significand = int.from_bytes(significand, byteorder="big", signed=True)
162
+ return decimal.Decimal(significand).scaleb(exponent)
163
+
164
+ def DECFLOAT_to_numpy_float64(self, exponent: int, significand: bytes) -> float64:
165
+ return numpy.float64(self.DECFLOAT_to_decimal(exponent, significand))
166
+
167
+ def INTERVAL_YEAR_MONTH_to_numpy_timedelta(self, months: int) -> timedelta64:
168
+ return numpy.timedelta64(months, "M")
169
+
170
+ def INTERVAL_DAY_TIME_int_to_numpy_timedelta(self, nanos: int) -> timedelta64:
171
+ return numpy.timedelta64(nanos, "ns")
172
+
173
+ def INTERVAL_DAY_TIME_int_to_timedelta(self, nanos: int) -> timedelta:
174
+ # Python timedelta only supports microsecond precision. We receive value in
175
+ # nanoseconds.
176
+ return timedelta(microseconds=nanos // 1000)
177
+
178
+ def INTERVAL_DAY_TIME_decimal_to_numpy_timedelta(self, value: bytes) -> timedelta64:
179
+ # Snowflake supports up to 9 digits leading field precision for the day-time
180
+ # interval. That when represented in nanoseconds can not be stored in a 64-bit
181
+ # integer. So we send these as Decimal128 from server to client.
182
+ # Arrow uses little-endian by default.
183
+ # https://arrow.apache.org/docs/format/Columnar.html#byte-order-endianness
184
+ nanos = int.from_bytes(value, byteorder="little", signed=True)
185
+ # Numpy timedelta only supports up to 64-bit integers, so we need to change the
186
+ # unit to milliseconds to avoid overflow.
187
+ # Max value received from server
188
+ # = 10**9 * NANOS_PER_DAY - 1
189
+ # = 86399999999999999999999 nanoseconds
190
+ # = 86399999999999999 milliseconds
191
+ # math.log2(86399999999999999) = 56.3 < 64
192
+ return numpy.timedelta64(nanos // 1_000_000, "ms")
193
+
194
+ def INTERVAL_DAY_TIME_decimal_to_timedelta(self, value: bytes) -> timedelta:
195
+ # Snowflake supports up to 9 digits leading field precision for the day-time
196
+ # interval. That when represented in nanoseconds can not be stored in a 64-bit
197
+ # integer. So we send these as Decimal128 from server to client.
198
+ # Arrow uses little-endian by default.
199
+ # https://arrow.apache.org/docs/format/Columnar.html#byte-order-endianness
200
+ nanos = int.from_bytes(value, byteorder="little", signed=True)
201
+ # Python timedelta only supports microsecond precision. We receive value in
202
+ # nanoseconds.
203
+ return timedelta(microseconds=nanos // 1000)