snowflake-connector-python 3.16.0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/connector/__init__.py +96 -0
- snowflake/connector/_query_context_cache.py +276 -0
- snowflake/connector/_sql_util.py +48 -0
- snowflake/connector/_utils.py +70 -0
- snowflake/connector/arrow_context.py +203 -0
- snowflake/connector/auth/__init__.py +53 -0
- snowflake/connector/auth/_auth.py +586 -0
- snowflake/connector/auth/_http_server.py +220 -0
- snowflake/connector/auth/_oauth_base.py +400 -0
- snowflake/connector/auth/by_plugin.py +219 -0
- snowflake/connector/auth/default.py +36 -0
- snowflake/connector/auth/idtoken.py +72 -0
- snowflake/connector/auth/keypair.py +222 -0
- snowflake/connector/auth/no_auth.py +39 -0
- snowflake/connector/auth/oauth.py +49 -0
- snowflake/connector/auth/oauth_code.py +479 -0
- snowflake/connector/auth/oauth_credentials.py +66 -0
- snowflake/connector/auth/okta.py +335 -0
- snowflake/connector/auth/pat.py +39 -0
- snowflake/connector/auth/usrpwdmfa.py +69 -0
- snowflake/connector/auth/webbrowser.py +502 -0
- snowflake/connector/auth/workload_identity.py +94 -0
- snowflake/connector/azure_storage_client.py +276 -0
- snowflake/connector/backoff_policies.py +141 -0
- snowflake/connector/bind_upload_agent.py +89 -0
- snowflake/connector/cache.py +696 -0
- snowflake/connector/compat.py +127 -0
- snowflake/connector/config_manager.py +496 -0
- snowflake/connector/connection.py +2273 -0
- snowflake/connector/connection_diagnostic.py +776 -0
- snowflake/connector/constants.py +442 -0
- snowflake/connector/converter.py +785 -0
- snowflake/connector/converter_issue23517.py +87 -0
- snowflake/connector/converter_null.py +14 -0
- snowflake/connector/converter_snowsql.py +205 -0
- snowflake/connector/cursor.py +1951 -0
- snowflake/connector/dbapi.py +53 -0
- snowflake/connector/description.py +19 -0
- snowflake/connector/direct_file_operation_utils.py +88 -0
- snowflake/connector/encryption_util.py +220 -0
- snowflake/connector/errorcode.py +91 -0
- snowflake/connector/errors.py +615 -0
- snowflake/connector/externals_utils/__init__.py +0 -0
- snowflake/connector/externals_utils/externals_setup.py +27 -0
- snowflake/connector/feature.py +4 -0
- snowflake/connector/file_compression_type.py +118 -0
- snowflake/connector/file_lock.py +72 -0
- snowflake/connector/file_transfer_agent.py +1215 -0
- snowflake/connector/file_util.py +153 -0
- snowflake/connector/gcs_storage_client.py +474 -0
- snowflake/connector/gzip_decoder.py +85 -0
- snowflake/connector/local_storage_client.py +90 -0
- snowflake/connector/log_configuration.py +60 -0
- snowflake/connector/logging_utils/__init__.py +0 -0
- snowflake/connector/logging_utils/filters.py +72 -0
- snowflake/connector/nanoarrow_arrow_iterator.cp313-win_amd64.pyd +0 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.cpp +60 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.cpp +21 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.hpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.cpp +557 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.hpp +98 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.cpp +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.hpp +115 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.cpp +1005 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.hpp +127 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.cpp +47 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.hpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.cpp +83 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.cpp +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.hpp +72 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.cpp +73 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.hpp +28 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.cpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.hpp +35 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IColumnConverter.hpp +17 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.cpp +23 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.hpp +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.cpp +71 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.hpp +56 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/LICENSE.txt +209 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.cpp +75 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.hpp +30 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.cpp +46 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.hpp +29 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.cpp +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.hpp +95 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.cpp +57 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.hpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.cpp +34 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.hpp +48 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.cpp +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.hpp +26 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.cpp +36 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.hpp +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.cpp +346 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.hpp +145 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/macros.hpp +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.cpp +65 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.hpp +68 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_accessors.h +101 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_alloc.h +127 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_assert.h +45 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_builder.h +1908 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_emitter.h +215 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_endian.h +125 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_epilogue.h +7 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_flatbuffers.h +55 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_identifier.h +148 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_iov.h +31 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_prologue.h +8 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_refmap.h +144 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_rtconfig.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_types.h +97 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_verifier.h +239 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/flatcc_portable.h +14 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/paligned_alloc.h +210 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pattributes.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic.h +84 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_pop.h +20 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_push.h +51 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian.h +206 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian_detect.h +118 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinline.h +19 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinttypes.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable.h +2 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable_basic.h +25 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstatic_assert.h +67 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdalign.h +162 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdint.h +898 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/punaligned.h +190 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pversion.h +6 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pwarnings.h +52 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc.c +3204 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.c +3217 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.h +3618 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.hpp +379 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_arrow_iterator.pyx +256 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.c +512 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.h +350 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.c +33273 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.h +397 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.cpp +100 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.hpp +49 -0
- snowflake/connector/network.py +1297 -0
- snowflake/connector/ocsp_asn1crypto.py +447 -0
- snowflake/connector/ocsp_snowflake.py +1883 -0
- snowflake/connector/options.py +131 -0
- snowflake/connector/pandas_tools.py +732 -0
- snowflake/connector/proxy.py +43 -0
- snowflake/connector/py.typed +0 -0
- snowflake/connector/result_batch.py +786 -0
- snowflake/connector/result_set.py +319 -0
- snowflake/connector/s3_storage_client.py +605 -0
- snowflake/connector/secret_detector.py +181 -0
- snowflake/connector/sf_dirs.py +64 -0
- snowflake/connector/sfbinaryformat.py +35 -0
- snowflake/connector/sfdatetime.py +345 -0
- snowflake/connector/snow_logging.py +144 -0
- snowflake/connector/sqlstate.py +9 -0
- snowflake/connector/ssd_internal_keys.py +30 -0
- snowflake/connector/ssl_wrap_socket.py +134 -0
- snowflake/connector/storage_client.py +478 -0
- snowflake/connector/telemetry.py +249 -0
- snowflake/connector/telemetry_oob.py +543 -0
- snowflake/connector/test_util.py +30 -0
- snowflake/connector/time_util.py +159 -0
- snowflake/connector/token_cache.py +402 -0
- snowflake/connector/tool/__init__.py +0 -0
- snowflake/connector/tool/dump_certs.py +57 -0
- snowflake/connector/tool/dump_ocsp_response.py +139 -0
- snowflake/connector/tool/dump_ocsp_response_cache.py +194 -0
- snowflake/connector/tool/probe_connection.py +69 -0
- snowflake/connector/url_util.py +49 -0
- snowflake/connector/util_text.py +301 -0
- snowflake/connector/vendored/__init__.py +3 -0
- snowflake/connector/vendored/requests/LICENSE +175 -0
- snowflake/connector/vendored/requests/__init__.py +169 -0
- snowflake/connector/vendored/requests/__version__.py +14 -0
- snowflake/connector/vendored/requests/_internal_utils.py +50 -0
- snowflake/connector/vendored/requests/adapters.py +537 -0
- snowflake/connector/vendored/requests/api.py +157 -0
- snowflake/connector/vendored/requests/auth.py +315 -0
- snowflake/connector/vendored/requests/certs.py +17 -0
- snowflake/connector/vendored/requests/compat.py +79 -0
- snowflake/connector/vendored/requests/cookies.py +561 -0
- snowflake/connector/vendored/requests/exceptions.py +140 -0
- snowflake/connector/vendored/requests/help.py +134 -0
- snowflake/connector/vendored/requests/hooks.py +33 -0
- snowflake/connector/vendored/requests/models.py +1033 -0
- snowflake/connector/vendored/requests/sessions.py +833 -0
- snowflake/connector/vendored/requests/status_codes.py +128 -0
- snowflake/connector/vendored/requests/structures.py +99 -0
- snowflake/connector/vendored/requests/utils.py +1093 -0
- snowflake/connector/vendored/urllib3/LICENSE.txt +21 -0
- snowflake/connector/vendored/urllib3/__init__.py +85 -0
- snowflake/connector/vendored/urllib3/_collections.py +355 -0
- snowflake/connector/vendored/urllib3/_version.py +2 -0
- snowflake/connector/vendored/urllib3/connection.py +572 -0
- snowflake/connector/vendored/urllib3/connectionpool.py +1137 -0
- snowflake/connector/vendored/urllib3/contrib/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/contrib/_appengine_environ.py +36 -0
- snowflake/connector/vendored/urllib3/contrib/_securetransport/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/contrib/_securetransport/bindings.py +519 -0
- snowflake/connector/vendored/urllib3/contrib/_securetransport/low_level.py +397 -0
- snowflake/connector/vendored/urllib3/contrib/appengine.py +314 -0
- snowflake/connector/vendored/urllib3/contrib/ntlmpool.py +130 -0
- snowflake/connector/vendored/urllib3/contrib/pyopenssl.py +509 -0
- snowflake/connector/vendored/urllib3/contrib/securetransport.py +920 -0
- snowflake/connector/vendored/urllib3/contrib/socks.py +216 -0
- snowflake/connector/vendored/urllib3/exceptions.py +323 -0
- snowflake/connector/vendored/urllib3/fields.py +274 -0
- snowflake/connector/vendored/urllib3/filepost.py +98 -0
- snowflake/connector/vendored/urllib3/packages/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/packages/backports/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/packages/backports/makefile.py +51 -0
- snowflake/connector/vendored/urllib3/packages/backports/weakref_finalize.py +155 -0
- snowflake/connector/vendored/urllib3/packages/six.py +1076 -0
- snowflake/connector/vendored/urllib3/poolmanager.py +540 -0
- snowflake/connector/vendored/urllib3/request.py +191 -0
- snowflake/connector/vendored/urllib3/response.py +885 -0
- snowflake/connector/vendored/urllib3/util/__init__.py +49 -0
- snowflake/connector/vendored/urllib3/util/connection.py +156 -0
- snowflake/connector/vendored/urllib3/util/proxy.py +57 -0
- snowflake/connector/vendored/urllib3/util/queue.py +22 -0
- snowflake/connector/vendored/urllib3/util/request.py +146 -0
- snowflake/connector/vendored/urllib3/util/response.py +107 -0
- snowflake/connector/vendored/urllib3/util/retry.py +620 -0
- snowflake/connector/vendored/urllib3/util/ssl_.py +495 -0
- snowflake/connector/vendored/urllib3/util/ssl_match_hostname.py +159 -0
- snowflake/connector/vendored/urllib3/util/ssltransport.py +221 -0
- snowflake/connector/vendored/urllib3/util/timeout.py +271 -0
- snowflake/connector/vendored/urllib3/util/url.py +435 -0
- snowflake/connector/vendored/urllib3/util/wait.py +152 -0
- snowflake/connector/version.py +3 -0
- snowflake/connector/wif_util.py +407 -0
- snowflake_connector_python-3.16.0.dist-info/METADATA +1475 -0
- snowflake_connector_python-3.16.0.dist-info/RECORD +246 -0
- snowflake_connector_python-3.16.0.dist-info/WHEEL +5 -0
- snowflake_connector_python-3.16.0.dist-info/entry_points.txt +4 -0
- snowflake_connector_python-3.16.0.dist-info/licenses/LICENSE.txt +202 -0
- snowflake_connector_python-3.16.0.dist-info/licenses/NOTICE +8 -0
- snowflake_connector_python-3.16.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import time
|
|
5
|
+
from logging import getLogger
|
|
6
|
+
from types import TracebackType
|
|
7
|
+
from typing import Callable, Iterator
|
|
8
|
+
|
|
9
|
+
logger = getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from threading import _Timer as Timer
|
|
13
|
+
except ImportError:
|
|
14
|
+
from threading import Timer
|
|
15
|
+
|
|
16
|
+
DEFAULT_MASTER_VALIDITY_IN_SECONDS = 4 * 60 * 60 # seconds
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HeartBeatTimer(Timer):
|
|
20
|
+
"""A thread which executes a function every client_session_keep_alive_heartbeat_frequency seconds."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self, client_session_keep_alive_heartbeat_frequency: int, f: Callable
|
|
24
|
+
) -> None:
|
|
25
|
+
interval = client_session_keep_alive_heartbeat_frequency
|
|
26
|
+
super().__init__(interval, f)
|
|
27
|
+
# Mark this as a daemon thread, so that it won't prevent Python from exiting.
|
|
28
|
+
self.daemon = True
|
|
29
|
+
|
|
30
|
+
def run(self) -> None:
|
|
31
|
+
while not self.finished.is_set():
|
|
32
|
+
self.finished.wait(self.interval)
|
|
33
|
+
if not self.finished.is_set():
|
|
34
|
+
try:
|
|
35
|
+
self.function()
|
|
36
|
+
except Exception as e:
|
|
37
|
+
logger.debug("failed to heartbeat: %s", e)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_time_millis() -> int:
|
|
41
|
+
"""Returns the current time in milliseconds."""
|
|
42
|
+
return int(time.time() * 1000)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TimerContextManager:
|
|
46
|
+
"""Context manager class to easily measure execution of a code block.
|
|
47
|
+
|
|
48
|
+
Once the context manager finishes, the class should be cast into an int to retrieve
|
|
49
|
+
result.
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
|
|
53
|
+
with TimerContextManager() as measured_time:
|
|
54
|
+
pass
|
|
55
|
+
download_metric = measured_time.get_timing_millis()
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self) -> None:
|
|
59
|
+
self._start: int | None = None
|
|
60
|
+
self._end: int | None = None
|
|
61
|
+
|
|
62
|
+
def __enter__(self) -> TimerContextManager:
|
|
63
|
+
self._start = get_time_millis()
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
def __exit__(
|
|
67
|
+
self,
|
|
68
|
+
exc_type: type[BaseException] | None,
|
|
69
|
+
exc_val: BaseException | None,
|
|
70
|
+
exc_tb: TracebackType | None,
|
|
71
|
+
) -> None:
|
|
72
|
+
self._end = get_time_millis()
|
|
73
|
+
|
|
74
|
+
def get_timing_millis(self) -> int:
|
|
75
|
+
"""Get measured timing in milliseconds."""
|
|
76
|
+
if self._start is None or self._end is None:
|
|
77
|
+
raise Exception(
|
|
78
|
+
"Trying to get timing before TimerContextManager has finished"
|
|
79
|
+
)
|
|
80
|
+
return self._end - self._start
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TimeoutBackoffCtx:
|
|
84
|
+
"""Base context for handling timeouts and backoffs on retries"""
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
max_retry_attempts: int | None = None,
|
|
89
|
+
timeout: int | None = None,
|
|
90
|
+
backoff_generator: Iterator | None = None,
|
|
91
|
+
) -> None:
|
|
92
|
+
self._backoff_generator = backoff_generator
|
|
93
|
+
|
|
94
|
+
self._max_retry_attempts = max_retry_attempts
|
|
95
|
+
# in seconds
|
|
96
|
+
self._timeout = timeout
|
|
97
|
+
|
|
98
|
+
self._current_retry_count = 0
|
|
99
|
+
self._current_sleep_time = self._advance_backoff()
|
|
100
|
+
self._start_time_millis = None
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def timeout(self) -> int | None:
|
|
104
|
+
return self._timeout
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def current_retry_count(self) -> int:
|
|
108
|
+
return int(self._current_retry_count)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def current_sleep_time(self) -> int:
|
|
112
|
+
return int(self._current_sleep_time)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def remaining_time_millis(self) -> int:
|
|
116
|
+
if self._start_time_millis is None:
|
|
117
|
+
raise TypeError(
|
|
118
|
+
"Start time not recorded in remaining_time_millis, call set_start_time first"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if self._timeout is None:
|
|
122
|
+
raise TypeError("Timeout is None in remaining_time_millis")
|
|
123
|
+
|
|
124
|
+
timeout_millis = self._timeout * 1000
|
|
125
|
+
elapsed_time_millis = get_time_millis() - self._start_time_millis
|
|
126
|
+
return timeout_millis - elapsed_time_millis
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def should_retry(self) -> bool:
|
|
130
|
+
"""Decides whether to retry connection."""
|
|
131
|
+
if self._timeout is not None and self._start_time_millis is None:
|
|
132
|
+
logger.warning(
|
|
133
|
+
"Timeout set in TimeoutBackoffCtx, but start time not recorded"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
timed_out = (
|
|
137
|
+
self.remaining_time_millis < 0 if self._timeout is not None else False
|
|
138
|
+
)
|
|
139
|
+
retry_attempts_exceeded = (
|
|
140
|
+
self._current_retry_count >= self._max_retry_attempts
|
|
141
|
+
if self._max_retry_attempts is not None
|
|
142
|
+
else False
|
|
143
|
+
)
|
|
144
|
+
return not timed_out and not retry_attempts_exceeded
|
|
145
|
+
|
|
146
|
+
def _advance_backoff(self) -> int:
|
|
147
|
+
return (
|
|
148
|
+
next(self._backoff_generator) if self._backoff_generator is not None else 0
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def set_start_time(self) -> None:
|
|
152
|
+
self._start_time_millis = get_time_millis()
|
|
153
|
+
|
|
154
|
+
def increment(self) -> None:
|
|
155
|
+
"""Updates retry count and sleep time for another retry"""
|
|
156
|
+
self._current_retry_count += 1
|
|
157
|
+
self._current_sleep_time = self._advance_backoff()
|
|
158
|
+
logger.debug(f"Update retry count to {self._current_retry_count}")
|
|
159
|
+
logger.debug(f"Update sleep time to {self._current_sleep_time} seconds")
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import codecs
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import stat
|
|
9
|
+
import sys
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, TypeVar
|
|
15
|
+
|
|
16
|
+
from .compat import IS_LINUX, IS_MACOS, IS_WINDOWS
|
|
17
|
+
from .file_lock import FileLock, FileLockError
|
|
18
|
+
from .options import installed_keyring, keyring
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TokenType(Enum):
|
|
25
|
+
ID_TOKEN = "ID_TOKEN"
|
|
26
|
+
MFA_TOKEN = "MFA_TOKEN"
|
|
27
|
+
OAUTH_ACCESS_TOKEN = "OAUTH_ACCESS_TOKEN"
|
|
28
|
+
OAUTH_REFRESH_TOKEN = "OAUTH_REFRESH_TOKEN"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _InvalidTokenKeyError(Exception):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class TokenKey:
|
|
37
|
+
user: str
|
|
38
|
+
host: str
|
|
39
|
+
tokenType: TokenType
|
|
40
|
+
|
|
41
|
+
def string_key(self) -> str:
|
|
42
|
+
if len(self.host) == 0:
|
|
43
|
+
raise _InvalidTokenKeyError("Invalid key, host is empty")
|
|
44
|
+
if len(self.user) == 0:
|
|
45
|
+
raise _InvalidTokenKeyError("Invalid key, user is empty")
|
|
46
|
+
return f"{self.host.upper()}:{self.user.upper()}:{self.tokenType.value}"
|
|
47
|
+
|
|
48
|
+
def hash_key(self) -> str:
|
|
49
|
+
m = hashlib.sha256()
|
|
50
|
+
m.update(self.string_key().encode(encoding="utf-8"))
|
|
51
|
+
return m.hexdigest()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _warn(warning: str) -> None:
|
|
55
|
+
logger.warning(warning)
|
|
56
|
+
print("Warning: " + warning, file=sys.stderr)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TokenCache(ABC):
|
|
60
|
+
@staticmethod
|
|
61
|
+
def make() -> TokenCache:
|
|
62
|
+
if IS_MACOS or IS_WINDOWS:
|
|
63
|
+
if not installed_keyring:
|
|
64
|
+
_warn(
|
|
65
|
+
"Dependency 'keyring' is not installed, cannot cache id token. You might experience "
|
|
66
|
+
"multiple authentication pop ups while using ExternalBrowser/OAuth/MFA Authenticator. To avoid "
|
|
67
|
+
"this please install keyring module using the following command:\n"
|
|
68
|
+
" pip install snowflake-connector-python[secure-local-storage]"
|
|
69
|
+
)
|
|
70
|
+
return NoopTokenCache()
|
|
71
|
+
return KeyringTokenCache()
|
|
72
|
+
|
|
73
|
+
if IS_LINUX:
|
|
74
|
+
cache = FileTokenCache.make()
|
|
75
|
+
if cache:
|
|
76
|
+
return cache
|
|
77
|
+
else:
|
|
78
|
+
_warn(
|
|
79
|
+
"Failed to initialize file based token cache. You might experience "
|
|
80
|
+
"multiple authentication pop ups while using ExternalBrowser/OAuth/MFA Authenticator."
|
|
81
|
+
)
|
|
82
|
+
return NoopTokenCache()
|
|
83
|
+
|
|
84
|
+
@abstractmethod
|
|
85
|
+
def store(self, key: TokenKey, token: str) -> None:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def retrieve(self, key: TokenKey) -> str | None:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def remove(self, key: TokenKey) -> None:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class _FileTokenCacheError(Exception):
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class _OwnershipError(_FileTokenCacheError):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class _PermissionsTooWideError(_FileTokenCacheError):
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class _CacheDirNotFoundError(_FileTokenCacheError):
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class _InvalidCacheDirError(_FileTokenCacheError):
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class _MalformedCacheFileError(_FileTokenCacheError):
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class _CacheFileReadError(_FileTokenCacheError):
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class _CacheFileWriteError(_FileTokenCacheError):
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class FileTokenCache(TokenCache):
|
|
130
|
+
@staticmethod
|
|
131
|
+
def make() -> FileTokenCache | None:
|
|
132
|
+
cache_dir = FileTokenCache.find_cache_dir()
|
|
133
|
+
if cache_dir is None:
|
|
134
|
+
logging.getLogger(__name__).debug(
|
|
135
|
+
"Failed to find suitable cache directory for token cache. File based token cache initialization failed."
|
|
136
|
+
)
|
|
137
|
+
return None
|
|
138
|
+
else:
|
|
139
|
+
return FileTokenCache(cache_dir)
|
|
140
|
+
|
|
141
|
+
def __init__(self, cache_dir: Path) -> None:
|
|
142
|
+
self.logger = logging.getLogger(__name__)
|
|
143
|
+
self.cache_dir: Path = cache_dir
|
|
144
|
+
|
|
145
|
+
def store(self, key: TokenKey, token: str) -> None:
|
|
146
|
+
try:
|
|
147
|
+
FileTokenCache.validate_cache_dir(self.cache_dir)
|
|
148
|
+
with FileLock(self.lock_file()):
|
|
149
|
+
cache = self._read_cache_file()
|
|
150
|
+
cache["tokens"][key.hash_key()] = token
|
|
151
|
+
self._write_cache_file(cache)
|
|
152
|
+
except _FileTokenCacheError as e:
|
|
153
|
+
self.logger.error(f"Failed to store token: {e=}")
|
|
154
|
+
except FileLockError as e:
|
|
155
|
+
self.logger.error(f"Unable to lock file lock: {e=}")
|
|
156
|
+
except _InvalidTokenKeyError as e:
|
|
157
|
+
self.logger.error(f"Failed to produce token key {e=}")
|
|
158
|
+
|
|
159
|
+
def retrieve(self, key: TokenKey) -> str | None:
|
|
160
|
+
try:
|
|
161
|
+
FileTokenCache.validate_cache_dir(self.cache_dir)
|
|
162
|
+
with FileLock(self.lock_file()):
|
|
163
|
+
cache = self._read_cache_file()
|
|
164
|
+
token = cache["tokens"].get(key.hash_key(), None)
|
|
165
|
+
if isinstance(token, str):
|
|
166
|
+
return token
|
|
167
|
+
else:
|
|
168
|
+
return None
|
|
169
|
+
except _FileTokenCacheError as e:
|
|
170
|
+
self.logger.error(f"Failed to retrieve token: {e=}")
|
|
171
|
+
return None
|
|
172
|
+
except FileLockError as e:
|
|
173
|
+
self.logger.error(f"Unable to lock file lock: {e=}")
|
|
174
|
+
return None
|
|
175
|
+
except _InvalidTokenKeyError as e:
|
|
176
|
+
self.logger.error(f"Failed to produce token key {e=}")
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
def remove(self, key: TokenKey) -> None:
|
|
180
|
+
try:
|
|
181
|
+
FileTokenCache.validate_cache_dir(self.cache_dir)
|
|
182
|
+
with FileLock(self.lock_file()):
|
|
183
|
+
cache = self._read_cache_file()
|
|
184
|
+
cache["tokens"].pop(key.hash_key(), None)
|
|
185
|
+
self._write_cache_file(cache)
|
|
186
|
+
except _FileTokenCacheError as e:
|
|
187
|
+
self.logger.error(f"Failed to remove token: {e=}")
|
|
188
|
+
except FileLockError as e:
|
|
189
|
+
self.logger.error(f"Unable to lock file lock: {e=}")
|
|
190
|
+
except _InvalidTokenKeyError as e:
|
|
191
|
+
self.logger.error(f"Failed to produce token key {e=}")
|
|
192
|
+
|
|
193
|
+
def cache_file(self) -> Path:
|
|
194
|
+
return self.cache_dir / "credential_cache_v1.json"
|
|
195
|
+
|
|
196
|
+
def lock_file(self) -> Path:
|
|
197
|
+
return self.cache_dir / "credential_cache_v1.json.lck"
|
|
198
|
+
|
|
199
|
+
def _read_cache_file(self) -> dict[str, dict[str, Any]]:
|
|
200
|
+
fd = -1
|
|
201
|
+
json_data = {"tokens": {}}
|
|
202
|
+
try:
|
|
203
|
+
fd = os.open(self.cache_file(), os.O_RDONLY)
|
|
204
|
+
self._ensure_permissions(fd, 0o600)
|
|
205
|
+
size = os.lseek(fd, 0, os.SEEK_END)
|
|
206
|
+
os.lseek(fd, 0, os.SEEK_SET)
|
|
207
|
+
data = os.read(fd, size)
|
|
208
|
+
json_data = json.loads(codecs.decode(data, "utf-8"))
|
|
209
|
+
except FileNotFoundError:
|
|
210
|
+
self.logger.debug(f"{self.cache_file()} not found")
|
|
211
|
+
except json.decoder.JSONDecodeError as e:
|
|
212
|
+
self.logger.warning(
|
|
213
|
+
f"Failed to decode json read from cache file {self.cache_file()}: {e.__class__.__name__}"
|
|
214
|
+
)
|
|
215
|
+
except UnicodeError as e:
|
|
216
|
+
self.logger.warning(
|
|
217
|
+
f"Failed to decode utf-8 read from cache file {self.cache_file()}: {e.__class__.__name__}"
|
|
218
|
+
)
|
|
219
|
+
except OSError as e:
|
|
220
|
+
self.logger.warning(f"Failed to read cache file {self.cache_file()}: {e}")
|
|
221
|
+
finally:
|
|
222
|
+
if fd > 0:
|
|
223
|
+
os.close(fd)
|
|
224
|
+
|
|
225
|
+
if "tokens" not in json_data or not isinstance(json_data["tokens"], dict):
|
|
226
|
+
json_data["tokens"] = {}
|
|
227
|
+
|
|
228
|
+
return json_data
|
|
229
|
+
|
|
230
|
+
def _write_cache_file(self, json_data: dict):
|
|
231
|
+
fd = -1
|
|
232
|
+
self.logger.debug(f"Writing cache file {self.cache_file()}")
|
|
233
|
+
try:
|
|
234
|
+
fd = os.open(
|
|
235
|
+
self.cache_file(), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600
|
|
236
|
+
)
|
|
237
|
+
self._ensure_permissions(fd, 0o600)
|
|
238
|
+
os.write(fd, codecs.encode(json.dumps(json_data), "utf-8"))
|
|
239
|
+
return json_data
|
|
240
|
+
except OSError as e:
|
|
241
|
+
raise _CacheFileWriteError("Failed to write cache file", e)
|
|
242
|
+
finally:
|
|
243
|
+
if fd > 0:
|
|
244
|
+
os.close(fd)
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def find_cache_dir() -> Path | None:
|
|
248
|
+
def lookup_env_dir(env_var: str, subpath_segments: list[str]) -> Path | None:
|
|
249
|
+
env_val = os.getenv(env_var)
|
|
250
|
+
if env_val is None:
|
|
251
|
+
logger.debug(
|
|
252
|
+
f"Environment variable {env_var} not set. Skipping it in cache directory lookup."
|
|
253
|
+
)
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
directory = Path(env_val)
|
|
257
|
+
|
|
258
|
+
if len(subpath_segments) > 0:
|
|
259
|
+
if not directory.exists():
|
|
260
|
+
logger.debug(
|
|
261
|
+
f"Path {str(directory)} does not exist. Skipping it in cache directory lookup."
|
|
262
|
+
)
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
if not directory.is_dir():
|
|
266
|
+
logger.debug(
|
|
267
|
+
f"Path {str(directory)} is not a directory. Skipping it in cache directory lookup."
|
|
268
|
+
)
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
for subpath in subpath_segments[:-1]:
|
|
272
|
+
directory = directory / subpath
|
|
273
|
+
directory.mkdir(exist_ok=True, mode=0o755)
|
|
274
|
+
|
|
275
|
+
directory = directory / subpath_segments[-1]
|
|
276
|
+
directory.mkdir(exist_ok=True, mode=0o700)
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
FileTokenCache.validate_cache_dir(directory)
|
|
280
|
+
return directory
|
|
281
|
+
except _FileTokenCacheError as e:
|
|
282
|
+
logger.debug(
|
|
283
|
+
f"Cache directory validation failed for {str(directory)} due to error '{e}'. Skipping it in cache directory lookup."
|
|
284
|
+
)
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
lookup_functions = [
|
|
288
|
+
lambda: lookup_env_dir("SF_TEMPORARY_CREDENTIAL_CACHE_DIR", []),
|
|
289
|
+
lambda: lookup_env_dir("XDG_CACHE_HOME", ["snowflake"]),
|
|
290
|
+
lambda: lookup_env_dir("HOME", [".cache", "snowflake"]),
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
for lf in lookup_functions:
|
|
294
|
+
cache_dir = lf()
|
|
295
|
+
if cache_dir:
|
|
296
|
+
return cache_dir
|
|
297
|
+
|
|
298
|
+
return None
|
|
299
|
+
|
|
300
|
+
@staticmethod
|
|
301
|
+
def validate_cache_dir(cache_dir: Path | None) -> None:
|
|
302
|
+
try:
|
|
303
|
+
statinfo = cache_dir.stat()
|
|
304
|
+
|
|
305
|
+
if cache_dir is None:
|
|
306
|
+
raise _CacheDirNotFoundError("Cache dir was not found")
|
|
307
|
+
|
|
308
|
+
if not stat.S_ISDIR(statinfo.st_mode):
|
|
309
|
+
raise _InvalidCacheDirError(f"Cache dir {cache_dir} is not a directory")
|
|
310
|
+
|
|
311
|
+
permissions = stat.S_IMODE(statinfo.st_mode)
|
|
312
|
+
if permissions != 0o700:
|
|
313
|
+
raise _PermissionsTooWideError(
|
|
314
|
+
f"Cache dir {cache_dir} has incorrect permissions. {permissions:o} != 0700"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
euid = os.geteuid()
|
|
318
|
+
if statinfo.st_uid != euid:
|
|
319
|
+
raise _OwnershipError(
|
|
320
|
+
f"Cache dir {cache_dir} has incorrect owner. {euid} != {statinfo.st_uid}"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
except FileNotFoundError:
|
|
324
|
+
raise _CacheDirNotFoundError(
|
|
325
|
+
f"Cache dir {cache_dir} was not found. Failed to stat."
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
def _ensure_permissions(self, fd: int, permissions: int) -> None:
|
|
329
|
+
try:
|
|
330
|
+
statinfo = os.fstat(fd)
|
|
331
|
+
actual_permissions = stat.S_IMODE(statinfo.st_mode)
|
|
332
|
+
|
|
333
|
+
if actual_permissions != permissions:
|
|
334
|
+
raise _PermissionsTooWideError(
|
|
335
|
+
f"Cache file {self.cache_file()} has incorrect permissions. {permissions:o} != {actual_permissions:o}"
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
euid = os.geteuid()
|
|
339
|
+
if statinfo.st_uid != euid:
|
|
340
|
+
raise _OwnershipError(
|
|
341
|
+
f"Cache file {self.cache_file()} has incorrect owner. {euid} != {statinfo.st_uid}"
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
except FileNotFoundError:
|
|
345
|
+
pass
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class KeyringTokenCache(TokenCache):
|
|
349
|
+
def __init__(self) -> None:
|
|
350
|
+
self.logger = logging.getLogger(__name__)
|
|
351
|
+
|
|
352
|
+
def store(self, key: TokenKey, token: str) -> None:
|
|
353
|
+
try:
|
|
354
|
+
keyring.set_password(
|
|
355
|
+
key.string_key(),
|
|
356
|
+
key.user.upper(),
|
|
357
|
+
token,
|
|
358
|
+
)
|
|
359
|
+
except _InvalidTokenKeyError as e:
|
|
360
|
+
self.logger.error(f"Could not retrieve {key.tokenType} from keyring, {e=}")
|
|
361
|
+
except keyring.errors.KeyringError as ke:
|
|
362
|
+
self.logger.error("Could not store id_token to keyring, %s", str(ke))
|
|
363
|
+
|
|
364
|
+
def retrieve(self, key: TokenKey) -> str | None:
|
|
365
|
+
try:
|
|
366
|
+
return keyring.get_password(
|
|
367
|
+
key.string_key(),
|
|
368
|
+
key.user.upper(),
|
|
369
|
+
)
|
|
370
|
+
except keyring.errors.KeyringError as ke:
|
|
371
|
+
self.logger.error(
|
|
372
|
+
"Could not retrieve {} from secure storage : {}".format(
|
|
373
|
+
key.tokenType.value, str(ke)
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
except _InvalidTokenKeyError as e:
|
|
377
|
+
self.logger.error(f"Could not retrieve {key.tokenType} from keyring, {e=}")
|
|
378
|
+
|
|
379
|
+
def remove(self, key: TokenKey) -> None:
|
|
380
|
+
try:
|
|
381
|
+
keyring.delete_password(
|
|
382
|
+
key.string_key(),
|
|
383
|
+
key.user.upper(),
|
|
384
|
+
)
|
|
385
|
+
except _InvalidTokenKeyError as e:
|
|
386
|
+
self.logger.error(f"Could not retrieve {key.tokenType} from keyring, {e=}")
|
|
387
|
+
except Exception as ex:
|
|
388
|
+
self.logger.error(
|
|
389
|
+
"Failed to delete credential in the keyring: err=[%s]", ex
|
|
390
|
+
)
|
|
391
|
+
pass
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class NoopTokenCache(TokenCache):
|
|
395
|
+
def store(self, key: TokenKey, token: str) -> None:
|
|
396
|
+
return None
|
|
397
|
+
|
|
398
|
+
def retrieve(self, key: TokenKey) -> str | None:
|
|
399
|
+
return None
|
|
400
|
+
|
|
401
|
+
def remove(self, key: TokenKey) -> None:
|
|
402
|
+
return None
|
|
File without changes
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from os import path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from snowflake.connector.ocsp_asn1crypto import SnowflakeOCSPAsn1Crypto
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from asn1crypto.x509 import Certificate
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> None:
|
|
16
|
+
"""Internal Tool: Extract certificate files in PEM."""
|
|
17
|
+
|
|
18
|
+
def help() -> None:
|
|
19
|
+
print(
|
|
20
|
+
"Extract certificate file. The target file can be a single file "
|
|
21
|
+
"or a directory including multiple certificates. The certificate "
|
|
22
|
+
"file format should be PEM."
|
|
23
|
+
)
|
|
24
|
+
print(
|
|
25
|
+
"""
|
|
26
|
+
Usage: {} <input file/dir>
|
|
27
|
+
""".format(
|
|
28
|
+
path.basename(sys.argv[0])
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
sys.exit(2)
|
|
32
|
+
|
|
33
|
+
if len(sys.argv) < 2:
|
|
34
|
+
help()
|
|
35
|
+
|
|
36
|
+
input_filename = sys.argv[1]
|
|
37
|
+
if path.isdir(input_filename):
|
|
38
|
+
files = [path.join(input_filename, f) for f in os.listdir(input_filename)]
|
|
39
|
+
else:
|
|
40
|
+
files = [input_filename]
|
|
41
|
+
|
|
42
|
+
for f in files:
|
|
43
|
+
open(f)
|
|
44
|
+
extract_certificate_file(f)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def extract_certificate_file(input_filename) -> None:
|
|
48
|
+
ocsp = SnowflakeOCSPAsn1Crypto()
|
|
49
|
+
cert_map: dict[bytes, Certificate] = {}
|
|
50
|
+
ocsp.read_cert_bundle(input_filename, cert_map)
|
|
51
|
+
|
|
52
|
+
for cert in cert_map.values():
|
|
53
|
+
print(f"serial #: {cert.serial_number}, name: {cert.subject.native}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
main()
|