snowflake-connector-python 4.8.0__cp313-cp313-macosx_15_0_arm64.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 +106 -0
- snowflake/connector/_connection_identifier_shape.py +136 -0
- snowflake/connector/_host_util.py +106 -0
- snowflake/connector/_ocsp_mode.py +68 -0
- snowflake/connector/_query_context_cache.py +276 -0
- snowflake/connector/_sql_util.py +48 -0
- snowflake/connector/_utils.py +387 -0
- snowflake/connector/aio/README.md +88 -0
- snowflake/connector/aio/__init__.py +163 -0
- snowflake/connector/aio/_azure_storage_client.py +212 -0
- snowflake/connector/aio/_bind_upload_agent.py +69 -0
- snowflake/connector/aio/_connection.py +1279 -0
- snowflake/connector/aio/_cursor.py +1410 -0
- snowflake/connector/aio/_description.py +5 -0
- snowflake/connector/aio/_direct_file_operation_utils.py +88 -0
- snowflake/connector/aio/_file_transfer_agent.py +316 -0
- snowflake/connector/aio/_gcs_storage_client.py +360 -0
- snowflake/connector/aio/_network.py +902 -0
- snowflake/connector/aio/_ocsp_asn1crypto.py +51 -0
- snowflake/connector/aio/_ocsp_snowflake.py +603 -0
- snowflake/connector/aio/_pandas_tools.py +561 -0
- snowflake/connector/aio/_result_batch.py +457 -0
- snowflake/connector/aio/_result_set.py +335 -0
- snowflake/connector/aio/_s3_storage_client.py +460 -0
- snowflake/connector/aio/_session_manager.py +579 -0
- snowflake/connector/aio/_storage_client.py +332 -0
- snowflake/connector/aio/_telemetry.py +97 -0
- snowflake/connector/aio/_time_util.py +57 -0
- snowflake/connector/aio/_wif_util.py +441 -0
- snowflake/connector/aio/auth/__init__.py +52 -0
- snowflake/connector/aio/auth/_auth.py +414 -0
- snowflake/connector/aio/auth/_by_plugin.py +131 -0
- snowflake/connector/aio/auth/_default.py +28 -0
- snowflake/connector/aio/auth/_idtoken.py +54 -0
- snowflake/connector/aio/auth/_keypair.py +64 -0
- snowflake/connector/aio/auth/_no_auth.py +31 -0
- snowflake/connector/aio/auth/_oauth.py +27 -0
- snowflake/connector/aio/auth/_oauth_code.py +118 -0
- snowflake/connector/aio/auth/_oauth_credentials.py +102 -0
- snowflake/connector/aio/auth/_okta.py +257 -0
- snowflake/connector/aio/auth/_pat.py +27 -0
- snowflake/connector/aio/auth/_usrpwdmfa.py +30 -0
- snowflake/connector/aio/auth/_webbrowser.py +406 -0
- snowflake/connector/aio/auth/_workload_identity.py +62 -0
- snowflake/connector/arrow_context.py +211 -0
- snowflake/connector/auth/__init__.py +53 -0
- snowflake/connector/auth/_auth.py +691 -0
- snowflake/connector/auth/_http_server.py +257 -0
- snowflake/connector/auth/_oauth_base.py +751 -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 +255 -0
- snowflake/connector/auth/no_auth.py +39 -0
- snowflake/connector/auth/oauth.py +49 -0
- snowflake/connector/auth/oauth_code.py +502 -0
- snowflake/connector/auth/oauth_credentials.py +71 -0
- snowflake/connector/auth/okta.py +366 -0
- snowflake/connector/auth/pat.py +40 -0
- snowflake/connector/auth/usrpwdmfa.py +69 -0
- snowflake/connector/auth/webbrowser.py +584 -0
- snowflake/connector/auth/workload_identity.py +190 -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 +725 -0
- snowflake/connector/compat.py +129 -0
- snowflake/connector/config_manager.py +562 -0
- snowflake/connector/connection.py +2954 -0
- snowflake/connector/connection_diagnostic.py +798 -0
- snowflake/connector/constants.py +604 -0
- snowflake/connector/converter.py +809 -0
- snowflake/connector/converter_issue23517.py +87 -0
- snowflake/connector/converter_null.py +14 -0
- snowflake/connector/converter_snowsql.py +205 -0
- snowflake/connector/crl.py +919 -0
- snowflake/connector/crl_cache.py +734 -0
- snowflake/connector/cursor.py +2195 -0
- snowflake/connector/dbapi.py +53 -0
- snowflake/connector/description.py +21 -0
- snowflake/connector/direct_file_operation_utils.py +88 -0
- snowflake/connector/encryption_util.py +220 -0
- snowflake/connector/errorcode.py +97 -0
- snowflake/connector/errors.py +685 -0
- snowflake/connector/externals_utils/__init__.py +0 -0
- snowflake/connector/externals_utils/externals_setup.py +38 -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 +1269 -0
- snowflake/connector/file_util.py +182 -0
- snowflake/connector/gcs_storage_client.py +476 -0
- snowflake/connector/gzip_decoder.py +85 -0
- snowflake/connector/interval_util.py +26 -0
- snowflake/connector/local_storage_client.py +90 -0
- snowflake/connector/log_configuration.py +98 -0
- snowflake/connector/logging_utils/__init__.py +0 -0
- snowflake/connector/logging_utils/filters.py +160 -0
- snowflake/connector/minicore/__init__.py +0 -0
- snowflake/connector/minicore/macos_aarch64/libsf_mini_core.dylib +0 -0
- snowflake/connector/nanoarrow_arrow_iterator.cpython-313-darwin.so +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 +606 -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 +1140 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.hpp +138 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.cpp +50 -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 +100 -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 +75 -0
- snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.hpp +57 -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 +40 -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 +67 -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 +263 -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 +98 -0
- snowflake/connector/nanoarrow_cpp/Logging/logging.hpp +53 -0
- snowflake/connector/network.py +1305 -0
- snowflake/connector/ocsp_asn1crypto.py +458 -0
- snowflake/connector/ocsp_snowflake.py +1986 -0
- snowflake/connector/options.py +200 -0
- snowflake/connector/os_details.py +88 -0
- snowflake/connector/pandas_tools.py +734 -0
- snowflake/connector/platform_detection.py +592 -0
- snowflake/connector/proxy.py +28 -0
- snowflake/connector/py.typed +0 -0
- snowflake/connector/result_batch.py +1113 -0
- snowflake/connector/result_set.py +398 -0
- snowflake/connector/s3_storage_client.py +695 -0
- snowflake/connector/secret_detector.py +256 -0
- snowflake/connector/session_manager.py +711 -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 +10 -0
- snowflake/connector/ssd_internal_keys.py +30 -0
- snowflake/connector/ssl_wrap_socket.py +600 -0
- snowflake/connector/storage_client.py +512 -0
- snowflake/connector/telemetry.py +270 -0
- snowflake/connector/telemetry_oob.py +544 -0
- snowflake/connector/test_util.py +30 -0
- snowflake/connector/time_util.py +159 -0
- snowflake/connector/token_cache.py +487 -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 +51 -0
- snowflake/connector/util_text.py +414 -0
- snowflake/connector/vendored/__init__.py +3 -0
- snowflake/connector/vendored/requests/LICENSE +175 -0
- snowflake/connector/vendored/requests/__init__.py +184 -0
- snowflake/connector/vendored/requests/__version__.py +14 -0
- snowflake/connector/vendored/requests/_internal_utils.py +50 -0
- snowflake/connector/vendored/requests/adapters.py +696 -0
- snowflake/connector/vendored/requests/api.py +157 -0
- snowflake/connector/vendored/requests/auth.py +314 -0
- snowflake/connector/vendored/requests/certs.py +17 -0
- snowflake/connector/vendored/requests/compat.py +106 -0
- snowflake/connector/vendored/requests/cookies.py +561 -0
- snowflake/connector/vendored/requests/exceptions.py +151 -0
- snowflake/connector/vendored/requests/help.py +134 -0
- snowflake/connector/vendored/requests/hooks.py +33 -0
- snowflake/connector/vendored/requests/models.py +1039 -0
- snowflake/connector/vendored/requests/packages.py +23 -0
- snowflake/connector/vendored/requests/sessions.py +831 -0
- snowflake/connector/vendored/requests/status_codes.py +128 -0
- snowflake/connector/vendored/requests/structures.py +99 -0
- snowflake/connector/vendored/requests/utils.py +1086 -0
- snowflake/connector/vendored/urllib3/LICENSE.txt +21 -0
- snowflake/connector/vendored/urllib3/__init__.py +211 -0
- snowflake/connector/vendored/urllib3/_base_connection.py +167 -0
- snowflake/connector/vendored/urllib3/_collections.py +486 -0
- snowflake/connector/vendored/urllib3/_request_methods.py +278 -0
- snowflake/connector/vendored/urllib3/_version.py +24 -0
- snowflake/connector/vendored/urllib3/connection.py +1108 -0
- snowflake/connector/vendored/urllib3/connectionpool.py +1191 -0
- snowflake/connector/vendored/urllib3/contrib/__init__.py +0 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/__init__.py +17 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/connection.py +260 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/emscripten_fetch_worker.js +110 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/fetch.py +726 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/request.py +22 -0
- snowflake/connector/vendored/urllib3/contrib/emscripten/response.py +281 -0
- snowflake/connector/vendored/urllib3/contrib/pyopenssl.py +579 -0
- snowflake/connector/vendored/urllib3/contrib/socks.py +228 -0
- snowflake/connector/vendored/urllib3/exceptions.py +335 -0
- snowflake/connector/vendored/urllib3/fields.py +341 -0
- snowflake/connector/vendored/urllib3/filepost.py +89 -0
- snowflake/connector/vendored/urllib3/http2/__init__.py +53 -0
- snowflake/connector/vendored/urllib3/http2/connection.py +356 -0
- snowflake/connector/vendored/urllib3/http2/probe.py +87 -0
- snowflake/connector/vendored/urllib3/poolmanager.py +653 -0
- snowflake/connector/vendored/urllib3/py.typed +2 -0
- snowflake/connector/vendored/urllib3/response.py +1493 -0
- snowflake/connector/vendored/urllib3/util/__init__.py +42 -0
- snowflake/connector/vendored/urllib3/util/connection.py +137 -0
- snowflake/connector/vendored/urllib3/util/proxy.py +43 -0
- snowflake/connector/vendored/urllib3/util/request.py +263 -0
- snowflake/connector/vendored/urllib3/util/response.py +101 -0
- snowflake/connector/vendored/urllib3/util/retry.py +557 -0
- snowflake/connector/vendored/urllib3/util/ssl_.py +477 -0
- snowflake/connector/vendored/urllib3/util/ssl_match_hostname.py +153 -0
- snowflake/connector/vendored/urllib3/util/ssltransport.py +271 -0
- snowflake/connector/vendored/urllib3/util/timeout.py +275 -0
- snowflake/connector/vendored/urllib3/util/url.py +469 -0
- snowflake/connector/vendored/urllib3/util/util.py +42 -0
- snowflake/connector/vendored/urllib3/util/wait.py +124 -0
- snowflake/connector/version.py +3 -0
- snowflake/connector/wif_util.py +738 -0
- snowflake_connector_python-4.8.0.dist-info/METADATA +1679 -0
- snowflake_connector_python-4.8.0.dist-info/RECORD +294 -0
- snowflake_connector_python-4.8.0.dist-info/WHEEL +5 -0
- snowflake_connector_python-4.8.0.dist-info/entry_points.txt +4 -0
- snowflake_connector_python-4.8.0.dist-info/licenses/LICENSE.txt +202 -0
- snowflake_connector_python-4.8.0.dist-info/licenses/NOTICE +8 -0
- snowflake_connector_python-4.8.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# Python Db API v2
|
|
3
|
+
#
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from functools import wraps
|
|
7
|
+
|
|
8
|
+
from ._utils import _core_loader
|
|
9
|
+
|
|
10
|
+
apilevel = "2.0"
|
|
11
|
+
threadsafety = 2
|
|
12
|
+
paramstyle = "pyformat"
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from logging import NullHandler
|
|
16
|
+
|
|
17
|
+
from snowflake.connector.externals_utils.externals_setup import setup_external_libraries
|
|
18
|
+
|
|
19
|
+
from .connection import SnowflakeConnection
|
|
20
|
+
from .cursor import DictCursor
|
|
21
|
+
from .dbapi import (
|
|
22
|
+
BINARY,
|
|
23
|
+
DATETIME,
|
|
24
|
+
NUMBER,
|
|
25
|
+
ROWID,
|
|
26
|
+
STRING,
|
|
27
|
+
Binary,
|
|
28
|
+
Date,
|
|
29
|
+
DateFromTicks,
|
|
30
|
+
Time,
|
|
31
|
+
TimeFromTicks,
|
|
32
|
+
Timestamp,
|
|
33
|
+
TimestampFromTicks,
|
|
34
|
+
)
|
|
35
|
+
from .errors import (
|
|
36
|
+
DatabaseError,
|
|
37
|
+
DataError,
|
|
38
|
+
Error,
|
|
39
|
+
IntegrityError,
|
|
40
|
+
InterfaceError,
|
|
41
|
+
InternalError,
|
|
42
|
+
NotSupportedError,
|
|
43
|
+
OperationalError,
|
|
44
|
+
ProgrammingError,
|
|
45
|
+
_Warning,
|
|
46
|
+
)
|
|
47
|
+
from .log_configuration import EasyLoggingConfigPython
|
|
48
|
+
from .version import VERSION
|
|
49
|
+
|
|
50
|
+
# Load the core library - failures are captured in core_loader and don't prevent module loading
|
|
51
|
+
try:
|
|
52
|
+
_core_loader.load()
|
|
53
|
+
except Exception:
|
|
54
|
+
# Silently continue if core loading fails - the error is already captured in core_loader
|
|
55
|
+
# This ensures the connector module loads even if the minicore library is unavailable
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
logging.getLogger(__name__).addHandler(NullHandler())
|
|
59
|
+
setup_external_libraries()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@wraps(SnowflakeConnection.__init__)
|
|
63
|
+
def Connect(**kwargs) -> SnowflakeConnection:
|
|
64
|
+
return SnowflakeConnection(**kwargs)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
connect = Connect
|
|
68
|
+
|
|
69
|
+
SNOWFLAKE_CONNECTOR_VERSION = ".".join(str(v) for v in VERSION[0:3])
|
|
70
|
+
__version__ = SNOWFLAKE_CONNECTOR_VERSION
|
|
71
|
+
|
|
72
|
+
__all__ = [
|
|
73
|
+
"SnowflakeConnection",
|
|
74
|
+
# Error handling
|
|
75
|
+
"Error",
|
|
76
|
+
"_Warning",
|
|
77
|
+
"InterfaceError",
|
|
78
|
+
"DatabaseError",
|
|
79
|
+
"NotSupportedError",
|
|
80
|
+
"DataError",
|
|
81
|
+
"IntegrityError",
|
|
82
|
+
"ProgrammingError",
|
|
83
|
+
"OperationalError",
|
|
84
|
+
"InternalError",
|
|
85
|
+
# Extended cursor
|
|
86
|
+
"DictCursor",
|
|
87
|
+
# DBAPI PEP 249 required exports
|
|
88
|
+
"connect",
|
|
89
|
+
"apilevel",
|
|
90
|
+
"threadsafety",
|
|
91
|
+
"paramstyle",
|
|
92
|
+
"Date",
|
|
93
|
+
"Time",
|
|
94
|
+
"Timestamp",
|
|
95
|
+
"Binary",
|
|
96
|
+
"DateFromTicks",
|
|
97
|
+
"TimeFromTicks",
|
|
98
|
+
"TimestampFromTicks",
|
|
99
|
+
"STRING",
|
|
100
|
+
"BINARY",
|
|
101
|
+
"NUMBER",
|
|
102
|
+
"DATETIME",
|
|
103
|
+
"ROWID",
|
|
104
|
+
# Extended data type (experimental)
|
|
105
|
+
"EasyLoggingConfigPython",
|
|
106
|
+
]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Capture user-supplied connection-identifier provenance for in-band telemetry.
|
|
3
|
+
|
|
4
|
+
The shape captured here is consumed by ``SnowflakeConnection._log_connection_identifier_shape``
|
|
5
|
+
and emitted as a single ``client_connection_identifier_shape`` telemetry event
|
|
6
|
+
per successful login. The capture function inspects the raw kwargs passed to
|
|
7
|
+
``SnowflakeConnection.__config`` before any normalization (host inference,
|
|
8
|
+
account stripping of ``.global``, region extraction from dotted account) runs,
|
|
9
|
+
so the shape reflects user intent rather than the final post-normalization
|
|
10
|
+
state of the connection.
|
|
11
|
+
|
|
12
|
+
Removal of this module and the emission it backs is tracked in SNOW-3548350
|
|
13
|
+
(target: 2026-11-30).
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Any, Mapping
|
|
19
|
+
|
|
20
|
+
from .telemetry import TelemetryField
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class ConnectionIdentifierShape:
|
|
25
|
+
"""Provenance of connection-identifier fields the user supplied.
|
|
26
|
+
|
|
27
|
+
All fields describe what the user supplied at the moment of input — they
|
|
28
|
+
reflect intent, not the final post-normalization state of the connection.
|
|
29
|
+
|
|
30
|
+
- ``account_provided``: the user explicitly set the ``account`` parameter
|
|
31
|
+
(via ``connect(account=...)``, kwargs, or ``connections.toml`` merged
|
|
32
|
+
into kwargs before ``__config`` runs).
|
|
33
|
+
- ``account_with_region``: the raw account string the user typed contained
|
|
34
|
+
a dot (e.g. ``"myacct.us-east-1"``), signaling the deprecated
|
|
35
|
+
``account.region`` embedded form. Set only on the raw input.
|
|
36
|
+
- ``account_org_provided``: the raw account string carried a dash in its
|
|
37
|
+
account portion (e.g. ``"myorg-myacct"``), signaling the org-prefixed
|
|
38
|
+
form. Region-portion dashes (e.g. the ``-east-`` in
|
|
39
|
+
``"myacct.us-east-1"``) are intentionally not counted; only the portion
|
|
40
|
+
before the first ``.`` is examined.
|
|
41
|
+
- ``region_provided``: the user explicitly set the ``region`` parameter as
|
|
42
|
+
a distinct kwarg. A region embedded inside a dotted account string is
|
|
43
|
+
NOT ``region_provided``; that's ``account_with_region``.
|
|
44
|
+
- ``host_provided``: the user explicitly set the ``host`` parameter.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
account_provided: bool = False
|
|
48
|
+
account_with_region: bool = False
|
|
49
|
+
account_org_provided: bool = False
|
|
50
|
+
region_provided: bool = False
|
|
51
|
+
host_provided: bool = False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _is_user_supplied_string(value: Any) -> bool:
|
|
55
|
+
"""A kwarg counts as user-supplied iff it's a non-empty string. Non-string
|
|
56
|
+
truthy values (e.g. an accidentally-passed ``True`` / ``int``) are not
|
|
57
|
+
treated as provided here — the regular ``__config`` validation will warn
|
|
58
|
+
or error on them later, and shape capture is best kept conservative."""
|
|
59
|
+
return isinstance(value, str) and value != ""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def record_input_shape(kwargs: Mapping[str, Any]) -> ConnectionIdentifierShape:
|
|
63
|
+
"""Capture the connection-identifier shape from the raw kwargs that
|
|
64
|
+
``SnowflakeConnection.__config`` receives.
|
|
65
|
+
|
|
66
|
+
Must be invoked before any normalization (the ``setattr`` loop in
|
|
67
|
+
``__config``, the ``construct_hostname`` call for host inference, or any
|
|
68
|
+
``parse_account`` stripping) — otherwise inferred values are
|
|
69
|
+
indistinguishable from user-supplied ones and ``host_provided`` /
|
|
70
|
+
``account_provided`` are no longer trustworthy.
|
|
71
|
+
"""
|
|
72
|
+
account = kwargs.get("account")
|
|
73
|
+
region = kwargs.get("region")
|
|
74
|
+
host = kwargs.get("host")
|
|
75
|
+
|
|
76
|
+
account_provided = _is_user_supplied_string(account)
|
|
77
|
+
account_with_region = False
|
|
78
|
+
account_org_provided = False
|
|
79
|
+
if account_provided:
|
|
80
|
+
# Only a dot at position > 0 splits the string into account / region;
|
|
81
|
+
# a leading dot (pathological input like ``.us-east-1``) leaves the
|
|
82
|
+
# whole string as the account portion. Mirrors gosnowflake's
|
|
83
|
+
# ``recordAccountShape`` (internal/config/dsn.go), which gates on
|
|
84
|
+
# ``i > 0`` so the dash search runs against the full raw value when
|
|
85
|
+
# there is no real account/region split.
|
|
86
|
+
dot_index = account.find(".")
|
|
87
|
+
if dot_index > 0:
|
|
88
|
+
account_with_region = True
|
|
89
|
+
account_portion = account[:dot_index]
|
|
90
|
+
else:
|
|
91
|
+
account_portion = account
|
|
92
|
+
# ``Contains(accountPortion, "-")`` in Go — any dash anywhere in the
|
|
93
|
+
# account portion (including position 0) flips the flag. The
|
|
94
|
+
# region-tail dashes are excluded by virtue of being outside
|
|
95
|
+
# ``account_portion``, not by a position check.
|
|
96
|
+
account_org_provided = "-" in account_portion
|
|
97
|
+
|
|
98
|
+
return ConnectionIdentifierShape(
|
|
99
|
+
account_provided=account_provided,
|
|
100
|
+
account_with_region=account_with_region,
|
|
101
|
+
account_org_provided=account_org_provided,
|
|
102
|
+
region_provided=_is_user_supplied_string(region),
|
|
103
|
+
host_provided=_is_user_supplied_string(host),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def build_shape_telemetry_message(shape: ConnectionIdentifierShape) -> dict[str, str]:
|
|
108
|
+
"""Build the wire-format payload for the ``client_connection_identifier_shape``
|
|
109
|
+
in-band telemetry event from a captured ``ConnectionIdentifierShape``.
|
|
110
|
+
|
|
111
|
+
Hoisted out of the sync / async ``_log_connection_identifier_shape``
|
|
112
|
+
emitters so both branches stay in lockstep — the five payload keys
|
|
113
|
+
(``account_provided``, ``account_with_region``, ``account_org_provided``,
|
|
114
|
+
``region_provided``, ``host_provided``) and their stringified-lowercase
|
|
115
|
+
boolean values are byte-identical across sibling drivers and must remain
|
|
116
|
+
so. Changing this builder is the only place that affects the wire format.
|
|
117
|
+
|
|
118
|
+
Booleans are stringified as lowercase ``"true"`` / ``"false"`` (matching
|
|
119
|
+
JSON-style boolean text) for cross-driver parity with gosnowflake's
|
|
120
|
+
``strconv.FormatBool`` and the JDBC / Node.js siblings.
|
|
121
|
+
|
|
122
|
+
TODO(SNOW-3548350): remove with the telemetry emission
|
|
123
|
+
(target: 2026-11-30).
|
|
124
|
+
"""
|
|
125
|
+
return {
|
|
126
|
+
TelemetryField.KEY_TYPE.value: TelemetryField.CONNECTION_IDENTIFIER_SHAPE.value,
|
|
127
|
+
TelemetryField.KEY_ACCOUNT_PROVIDED.value: str(shape.account_provided).lower(),
|
|
128
|
+
TelemetryField.KEY_ACCOUNT_WITH_REGION.value: str(
|
|
129
|
+
shape.account_with_region
|
|
130
|
+
).lower(),
|
|
131
|
+
TelemetryField.KEY_ACCOUNT_ORG_PROVIDED.value: str(
|
|
132
|
+
shape.account_org_provided
|
|
133
|
+
).lower(),
|
|
134
|
+
TelemetryField.KEY_REGION_PROVIDED.value: str(shape.region_provided).lower(),
|
|
135
|
+
TelemetryField.KEY_HOST_PROVIDED.value: str(shape.host_provided).lower(),
|
|
136
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
"""Host normalization and recognition shared by every subsystem that turns a
|
|
7
|
+
connection host into the authority of a URL the connector builds itself.
|
|
8
|
+
|
|
9
|
+
Kept subsystem-agnostic so that WORKLOAD_IDENTITY attestation and OCSP cache-URL
|
|
10
|
+
derivation match hosts identically; a second, slightly different rule in either
|
|
11
|
+
place is what this module exists to prevent. The suffix list itself lives in
|
|
12
|
+
``constants`` so the data stays next to the connector's other TLD constants.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Iterable
|
|
18
|
+
|
|
19
|
+
from .constants import _SNOWFLAKE_ALLOWED_HOST_SUFFIXES
|
|
20
|
+
|
|
21
|
+
# Maps only ASCII A-Z to a-z.
|
|
22
|
+
#
|
|
23
|
+
# Deliberately not str.lower(): full Unicode case folding maps some characters
|
|
24
|
+
# into ASCII, so a host containing U+212A KELVIN SIGN folds
|
|
25
|
+
# "snowfla<U+212A>ecomputing.com" into "snowflakecomputing.com" and would match a
|
|
26
|
+
# recognized suffix while DNS still resolves the original, unrelated name.
|
|
27
|
+
# Leaving non-ASCII untouched lets is_ldh_host reject it instead.
|
|
28
|
+
_ASCII_LOWERCASE_TABLE = str.maketrans(
|
|
29
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Characters legal in a DNS hostname label, plus '_': account labels
|
|
33
|
+
# legitimately contain one.
|
|
34
|
+
_LDH_CHARACTERS = frozenset("abcdefghijklmnopqrstuvwxyz0123456789-_")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def normalize_host(host: str | None) -> str:
|
|
38
|
+
"""Normalizes a host (or a configured host suffix) into the single string that
|
|
39
|
+
every subsequent check and every derived URL must use.
|
|
40
|
+
|
|
41
|
+
Trims, lower-cases ASCII only, drops everything from the first ':' onward,
|
|
42
|
+
then strips exactly one trailing '.' (FQDN form).
|
|
43
|
+
|
|
44
|
+
The port must be dropped before the trailing dot: a host in FQDN form
|
|
45
|
+
carrying an explicit port ("acct.snowflakecomputing.com.:443") still has the
|
|
46
|
+
dot immediately before the colon, and removing the dot first would leave it
|
|
47
|
+
attached to the label and match no suffix.
|
|
48
|
+
"""
|
|
49
|
+
if not host:
|
|
50
|
+
return ""
|
|
51
|
+
normalized = host.strip().translate(_ASCII_LOWERCASE_TABLE)
|
|
52
|
+
if ":" in normalized:
|
|
53
|
+
normalized = normalized.split(":", 1)[0]
|
|
54
|
+
if normalized.endswith("."):
|
|
55
|
+
normalized = normalized[:-1]
|
|
56
|
+
return normalized
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_ldh_host(normalized_host: str) -> bool:
|
|
60
|
+
"""Whether an already-normalized host is made only of characters legal in a
|
|
61
|
+
DNS hostname: every dot-separated label non-empty and drawn from
|
|
62
|
+
``[a-z0-9_-]``.
|
|
63
|
+
|
|
64
|
+
This is an allow-list, not a list of forbidden delimiters, and that
|
|
65
|
+
distinction is load-bearing. A host is only safe to interpolate into a URL
|
|
66
|
+
authority if it cannot carry a character that some URL parser treats as
|
|
67
|
+
ending the host, and enumerating such characters does not work: parsers
|
|
68
|
+
variously terminate the authority at a space, ';', "'", a percent-escape, or
|
|
69
|
+
a full-width look-alike of '.', '#' or '?'. Since a host whose trailing
|
|
70
|
+
labels are a recognized Snowflake domain can still begin with an unrelated
|
|
71
|
+
name, a parser that stops early resolves that unrelated name instead.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
normalized_host: output of :func:`normalize_host`.
|
|
75
|
+
"""
|
|
76
|
+
if not normalized_host:
|
|
77
|
+
return False
|
|
78
|
+
return all(
|
|
79
|
+
label and _LDH_CHARACTERS.issuperset(label)
|
|
80
|
+
for label in normalized_host.split(".")
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def has_recognized_snowflake_suffix(
|
|
85
|
+
normalized_host: str,
|
|
86
|
+
extra_suffixes: Iterable[str] | None = None,
|
|
87
|
+
) -> bool:
|
|
88
|
+
"""Whether an already-normalized host ends at one of the recognized Snowflake
|
|
89
|
+
suffixes, matched on a label boundary so that only a listed suffix and its
|
|
90
|
+
subdomains qualify. The bare apex is accepted by the equality branch.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
normalized_host: output of :func:`normalize_host`.
|
|
94
|
+
extra_suffixes: additional already-normalized suffixes to accept.
|
|
95
|
+
Additive only: callers cannot use this to disable the built-in list.
|
|
96
|
+
"""
|
|
97
|
+
if not normalized_host:
|
|
98
|
+
return False
|
|
99
|
+
suffixes: tuple[str, ...] = _SNOWFLAKE_ALLOWED_HOST_SUFFIXES
|
|
100
|
+
if extra_suffixes:
|
|
101
|
+
suffixes = (*suffixes, *extra_suffixes)
|
|
102
|
+
return any(
|
|
103
|
+
normalized_host == suffix or normalized_host.endswith(f".{suffix}")
|
|
104
|
+
for suffix in suffixes
|
|
105
|
+
if suffix
|
|
106
|
+
)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any, Collection
|
|
5
|
+
|
|
6
|
+
from .constants import OCSPMode
|
|
7
|
+
|
|
8
|
+
# ocsp_fail_open True/False opts into OCSP. None is the stored default and
|
|
9
|
+
# is not an opt-in. disable_ocsp_checks / insecure_mode only turn OCSP off
|
|
10
|
+
# when True; their stored False default is not an opt-in. Cache/timeout
|
|
11
|
+
# knobs never enable OCSP.
|
|
12
|
+
OCSP_MODE_PARAMS = frozenset({"disable_ocsp_checks", "ocsp_fail_open", "insecure_mode"})
|
|
13
|
+
OCSP_SUPPORT_PARAMS = frozenset(
|
|
14
|
+
{"ocsp_response_cache_filename", "ocsp_root_certs_dict_lock_timeout"}
|
|
15
|
+
)
|
|
16
|
+
OCSP_EXPLICIT_PARAMS = OCSP_MODE_PARAMS | OCSP_SUPPORT_PARAMS
|
|
17
|
+
|
|
18
|
+
IGNORED_OCSP_SUPPORT_PARAMS_WARNING = (
|
|
19
|
+
"OCSP is disabled unless you opt in. The following OCSP setting(s) will be "
|
|
20
|
+
"ignored because none of ocsp_fail_open, disable_ocsp_checks, or "
|
|
21
|
+
"insecure_mode was set: {params}. Set ocsp_fail_open=True or "
|
|
22
|
+
"ocsp_fail_open=False to enable OCSP."
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def snapshot_ocsp_explicit_params(params: Mapping[str, Any]) -> frozenset[str]:
|
|
27
|
+
return frozenset(name for name in OCSP_EXPLICIT_PARAMS if name in params)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_ocsp_mode(
|
|
31
|
+
*,
|
|
32
|
+
explicit: Collection[str],
|
|
33
|
+
disable_ocsp_checks: bool,
|
|
34
|
+
ocsp_fail_open: bool | None,
|
|
35
|
+
insecure_mode: bool | None = None,
|
|
36
|
+
) -> OCSPMode:
|
|
37
|
+
"""Resolve OCSP mode from explicit user/toml presence plus stored values.
|
|
38
|
+
|
|
39
|
+
``ocsp_fail_open`` is the only opt-in: True = fail-open, False =
|
|
40
|
+
fail-closed. None is the stored default and does not enable OCSP, even
|
|
41
|
+
when the key is present (a caller forwarding DEFAULT_CONFIGURATION).
|
|
42
|
+
|
|
43
|
+
Connection-property disable (``disable_ocsp_checks=True`` or
|
|
44
|
+
``insecure_mode=True``) always turns OCSP off, including when
|
|
45
|
+
``ocsp_fail_open`` is True/False. Presence of
|
|
46
|
+
``disable_ocsp_checks=False`` or ``insecure_mode=False`` is not an
|
|
47
|
+
opt-in; those are the stored defaults.
|
|
48
|
+
"""
|
|
49
|
+
if "disable_ocsp_checks" in explicit:
|
|
50
|
+
disable_value = disable_ocsp_checks
|
|
51
|
+
elif "insecure_mode" in explicit:
|
|
52
|
+
disable_value = (
|
|
53
|
+
insecure_mode if insecure_mode is not None else disable_ocsp_checks
|
|
54
|
+
)
|
|
55
|
+
else:
|
|
56
|
+
disable_value = False
|
|
57
|
+
|
|
58
|
+
if disable_value:
|
|
59
|
+
return OCSPMode.DISABLE_OCSP_CHECKS
|
|
60
|
+
if ocsp_fail_open is not None:
|
|
61
|
+
return OCSPMode.FAIL_OPEN if ocsp_fail_open else OCSPMode.FAIL_CLOSED
|
|
62
|
+
return OCSPMode.DISABLE_OCSP_CHECKS
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def ignored_ocsp_support_params(explicit: Collection[str]) -> tuple[str, ...]:
|
|
66
|
+
if any(name in explicit for name in OCSP_MODE_PARAMS):
|
|
67
|
+
return ()
|
|
68
|
+
return tuple(sorted(name for name in OCSP_SUPPORT_PARAMS if name in explicit))
|
|
@@ -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)
|