snowflake-connector-python 4.4.0__cp314-cp314-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 (302) hide show
  1. snowflake/connector/__init__.py +118 -0
  2. snowflake/connector/_query_context_cache.py +276 -0
  3. snowflake/connector/_sql_util.py +48 -0
  4. snowflake/connector/_utils.py +353 -0
  5. snowflake/connector/aio/README.md +88 -0
  6. snowflake/connector/aio/__init__.py +163 -0
  7. snowflake/connector/aio/_azure_storage_client.py +227 -0
  8. snowflake/connector/aio/_bind_upload_agent.py +69 -0
  9. snowflake/connector/aio/_connection.py +1216 -0
  10. snowflake/connector/aio/_cursor.py +1402 -0
  11. snowflake/connector/aio/_description.py +5 -0
  12. snowflake/connector/aio/_direct_file_operation_utils.py +88 -0
  13. snowflake/connector/aio/_file_transfer_agent.py +316 -0
  14. snowflake/connector/aio/_gcs_storage_client.py +360 -0
  15. snowflake/connector/aio/_network.py +860 -0
  16. snowflake/connector/aio/_ocsp_asn1crypto.py +51 -0
  17. snowflake/connector/aio/_ocsp_snowflake.py +608 -0
  18. snowflake/connector/aio/_pandas_tools.py +561 -0
  19. snowflake/connector/aio/_result_batch.py +442 -0
  20. snowflake/connector/aio/_result_set.py +309 -0
  21. snowflake/connector/aio/_s3_storage_client.py +460 -0
  22. snowflake/connector/aio/_session_manager.py +588 -0
  23. snowflake/connector/aio/_storage_client.py +331 -0
  24. snowflake/connector/aio/_telemetry.py +97 -0
  25. snowflake/connector/aio/_time_util.py +57 -0
  26. snowflake/connector/aio/_wif_util.py +324 -0
  27. snowflake/connector/aio/auth/__init__.py +52 -0
  28. snowflake/connector/aio/auth/_auth.py +406 -0
  29. snowflake/connector/aio/auth/_by_plugin.py +131 -0
  30. snowflake/connector/aio/auth/_default.py +28 -0
  31. snowflake/connector/aio/auth/_idtoken.py +54 -0
  32. snowflake/connector/aio/auth/_keypair.py +60 -0
  33. snowflake/connector/aio/auth/_no_auth.py +31 -0
  34. snowflake/connector/aio/auth/_oauth.py +27 -0
  35. snowflake/connector/aio/auth/_oauth_code.py +117 -0
  36. snowflake/connector/aio/auth/_oauth_credentials.py +100 -0
  37. snowflake/connector/aio/auth/_okta.py +247 -0
  38. snowflake/connector/aio/auth/_pat.py +27 -0
  39. snowflake/connector/aio/auth/_usrpwdmfa.py +30 -0
  40. snowflake/connector/aio/auth/_webbrowser.py +405 -0
  41. snowflake/connector/aio/auth/_workload_identity.py +60 -0
  42. snowflake/connector/arrow_context.py +211 -0
  43. snowflake/connector/auth/__init__.py +53 -0
  44. snowflake/connector/auth/_auth.py +669 -0
  45. snowflake/connector/auth/_http_server.py +257 -0
  46. snowflake/connector/auth/_oauth_base.py +472 -0
  47. snowflake/connector/auth/by_plugin.py +219 -0
  48. snowflake/connector/auth/default.py +36 -0
  49. snowflake/connector/auth/idtoken.py +72 -0
  50. snowflake/connector/auth/keypair.py +225 -0
  51. snowflake/connector/auth/no_auth.py +39 -0
  52. snowflake/connector/auth/oauth.py +49 -0
  53. snowflake/connector/auth/oauth_code.py +490 -0
  54. snowflake/connector/auth/oauth_credentials.py +71 -0
  55. snowflake/connector/auth/okta.py +339 -0
  56. snowflake/connector/auth/pat.py +40 -0
  57. snowflake/connector/auth/usrpwdmfa.py +69 -0
  58. snowflake/connector/auth/webbrowser.py +514 -0
  59. snowflake/connector/auth/workload_identity.py +110 -0
  60. snowflake/connector/azure_storage_client.py +278 -0
  61. snowflake/connector/backoff_policies.py +141 -0
  62. snowflake/connector/bind_upload_agent.py +89 -0
  63. snowflake/connector/cache.py +696 -0
  64. snowflake/connector/compat.py +127 -0
  65. snowflake/connector/config_manager.py +553 -0
  66. snowflake/connector/connection.py +2671 -0
  67. snowflake/connector/connection_diagnostic.py +793 -0
  68. snowflake/connector/constants.py +460 -0
  69. snowflake/connector/converter.py +809 -0
  70. snowflake/connector/converter_issue23517.py +87 -0
  71. snowflake/connector/converter_null.py +14 -0
  72. snowflake/connector/converter_snowsql.py +205 -0
  73. snowflake/connector/crl.py +919 -0
  74. snowflake/connector/crl_cache.py +734 -0
  75. snowflake/connector/cursor.py +2099 -0
  76. snowflake/connector/dbapi.py +53 -0
  77. snowflake/connector/description.py +21 -0
  78. snowflake/connector/direct_file_operation_utils.py +88 -0
  79. snowflake/connector/encryption_util.py +220 -0
  80. snowflake/connector/errorcode.py +94 -0
  81. snowflake/connector/errors.py +666 -0
  82. snowflake/connector/externals_utils/__init__.py +0 -0
  83. snowflake/connector/externals_utils/externals_setup.py +30 -0
  84. snowflake/connector/feature.py +4 -0
  85. snowflake/connector/file_compression_type.py +118 -0
  86. snowflake/connector/file_lock.py +72 -0
  87. snowflake/connector/file_transfer_agent.py +1244 -0
  88. snowflake/connector/file_util.py +153 -0
  89. snowflake/connector/gcs_storage_client.py +476 -0
  90. snowflake/connector/gzip_decoder.py +85 -0
  91. snowflake/connector/interval_util.py +26 -0
  92. snowflake/connector/local_storage_client.py +90 -0
  93. snowflake/connector/log_configuration.py +62 -0
  94. snowflake/connector/logging_utils/__init__.py +0 -0
  95. snowflake/connector/logging_utils/filters.py +72 -0
  96. snowflake/connector/minicore/__init__.py +0 -0
  97. snowflake/connector/minicore/aix_ppc64/libsf_mini_core.a +0 -0
  98. snowflake/connector/minicore/aix_ppc64/libsf_mini_core_static.a +0 -0
  99. snowflake/connector/minicore/linux_aarch64_glibc/libsf_mini_core.so +0 -0
  100. snowflake/connector/minicore/linux_aarch64_musl/libsf_mini_core.so +0 -0
  101. snowflake/connector/minicore/linux_x86_64_glibc/libsf_mini_core.so +0 -0
  102. snowflake/connector/minicore/linux_x86_64_musl/libsf_mini_core.so +0 -0
  103. snowflake/connector/minicore/macos_aarch64/libsf_mini_core.dylib +0 -0
  104. snowflake/connector/minicore/macos_x86_64/libsf_mini_core.dylib +0 -0
  105. snowflake/connector/minicore/windows_x86_64/sf_mini_core.dll +0 -0
  106. snowflake/connector/minicore/windows_x86_64/sf_mini_core_static.lib +0 -0
  107. snowflake/connector/nanoarrow_arrow_iterator.cp314-win_amd64.pyd +0 -0
  108. snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.cpp +60 -0
  109. snowflake/connector/nanoarrow_cpp/ArrowIterator/ArrayConverter.hpp +29 -0
  110. snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.cpp +19 -0
  111. snowflake/connector/nanoarrow_cpp/ArrowIterator/BinaryConverter.hpp +26 -0
  112. snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.cpp +21 -0
  113. snowflake/connector/nanoarrow_cpp/ArrowIterator/BooleanConverter.hpp +23 -0
  114. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.cpp +570 -0
  115. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.hpp +98 -0
  116. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.cpp +125 -0
  117. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowIterator.hpp +115 -0
  118. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.cpp +1140 -0
  119. snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowTableIterator.hpp +138 -0
  120. snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.cpp +47 -0
  121. snowflake/connector/nanoarrow_cpp/ArrowIterator/DateConverter.hpp +46 -0
  122. snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.cpp +83 -0
  123. snowflake/connector/nanoarrow_cpp/ArrowIterator/DecFloatConverter.hpp +35 -0
  124. snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.cpp +97 -0
  125. snowflake/connector/nanoarrow_cpp/ArrowIterator/DecimalConverter.hpp +72 -0
  126. snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.cpp +73 -0
  127. snowflake/connector/nanoarrow_cpp/ArrowIterator/FixedSizeListConverter.hpp +28 -0
  128. snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.cpp +30 -0
  129. snowflake/connector/nanoarrow_cpp/ArrowIterator/FloatConverter.hpp +35 -0
  130. snowflake/connector/nanoarrow_cpp/ArrowIterator/IColumnConverter.hpp +17 -0
  131. snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.cpp +23 -0
  132. snowflake/connector/nanoarrow_cpp/ArrowIterator/IntConverter.hpp +45 -0
  133. snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.cpp +75 -0
  134. snowflake/connector/nanoarrow_cpp/ArrowIterator/IntervalConverter.hpp +57 -0
  135. snowflake/connector/nanoarrow_cpp/ArrowIterator/LICENSE.txt +209 -0
  136. snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.cpp +75 -0
  137. snowflake/connector/nanoarrow_cpp/ArrowIterator/MapConverter.hpp +30 -0
  138. snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.cpp +46 -0
  139. snowflake/connector/nanoarrow_cpp/ArrowIterator/ObjectConverter.hpp +29 -0
  140. snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.cpp +8 -0
  141. snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Common.hpp +95 -0
  142. snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.cpp +57 -0
  143. snowflake/connector/nanoarrow_cpp/ArrowIterator/Python/Helpers.hpp +36 -0
  144. snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.cpp +34 -0
  145. snowflake/connector/nanoarrow_cpp/ArrowIterator/SnowflakeType.hpp +48 -0
  146. snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.cpp +19 -0
  147. snowflake/connector/nanoarrow_cpp/ArrowIterator/StringConverter.hpp +26 -0
  148. snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.cpp +36 -0
  149. snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeConverter.hpp +31 -0
  150. snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.cpp +346 -0
  151. snowflake/connector/nanoarrow_cpp/ArrowIterator/TimeStampConverter.hpp +145 -0
  152. snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/macros.hpp +14 -0
  153. snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.cpp +67 -0
  154. snowflake/connector/nanoarrow_cpp/ArrowIterator/Util/time.hpp +68 -0
  155. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_accessors.h +101 -0
  156. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_alloc.h +127 -0
  157. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_assert.h +45 -0
  158. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_builder.h +1908 -0
  159. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_emitter.h +215 -0
  160. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_endian.h +125 -0
  161. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_epilogue.h +7 -0
  162. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_flatbuffers.h +55 -0
  163. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_identifier.h +148 -0
  164. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_iov.h +31 -0
  165. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_prologue.h +8 -0
  166. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_refmap.h +144 -0
  167. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_rtconfig.h +162 -0
  168. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_types.h +97 -0
  169. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/flatcc_verifier.h +239 -0
  170. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/flatcc_portable.h +14 -0
  171. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/paligned_alloc.h +210 -0
  172. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pattributes.h +84 -0
  173. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic.h +84 -0
  174. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_pop.h +20 -0
  175. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pdiagnostic_push.h +51 -0
  176. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian.h +206 -0
  177. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pendian_detect.h +118 -0
  178. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinline.h +19 -0
  179. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pinttypes.h +52 -0
  180. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable.h +2 -0
  181. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/portable_basic.h +25 -0
  182. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstatic_assert.h +67 -0
  183. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdalign.h +162 -0
  184. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pstdint.h +898 -0
  185. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/punaligned.h +190 -0
  186. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pversion.h +6 -0
  187. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc/portable/pwarnings.h +52 -0
  188. snowflake/connector/nanoarrow_cpp/ArrowIterator/flatcc.c +3204 -0
  189. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.c +3217 -0
  190. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.h +3618 -0
  191. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow.hpp +379 -0
  192. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_arrow_iterator.pyx +262 -0
  193. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.c +512 -0
  194. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_device.h +350 -0
  195. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.c +33273 -0
  196. snowflake/connector/nanoarrow_cpp/ArrowIterator/nanoarrow_ipc.h +397 -0
  197. snowflake/connector/nanoarrow_cpp/Logging/logging.cpp +100 -0
  198. snowflake/connector/nanoarrow_cpp/Logging/logging.hpp +49 -0
  199. snowflake/connector/network.py +1227 -0
  200. snowflake/connector/ocsp_asn1crypto.py +447 -0
  201. snowflake/connector/ocsp_snowflake.py +1939 -0
  202. snowflake/connector/options.py +179 -0
  203. snowflake/connector/os_details.py +88 -0
  204. snowflake/connector/pandas_tools.py +734 -0
  205. snowflake/connector/platform_detection.py +555 -0
  206. snowflake/connector/proxy.py +28 -0
  207. snowflake/connector/py.typed +0 -0
  208. snowflake/connector/result_batch.py +917 -0
  209. snowflake/connector/result_set.py +340 -0
  210. snowflake/connector/s3_storage_client.py +614 -0
  211. snowflake/connector/secret_detector.py +181 -0
  212. snowflake/connector/session_manager.py +650 -0
  213. snowflake/connector/sf_dirs.py +64 -0
  214. snowflake/connector/sfbinaryformat.py +35 -0
  215. snowflake/connector/sfdatetime.py +345 -0
  216. snowflake/connector/snow_logging.py +144 -0
  217. snowflake/connector/sqlstate.py +9 -0
  218. snowflake/connector/ssd_internal_keys.py +30 -0
  219. snowflake/connector/ssl_wrap_socket.py +281 -0
  220. snowflake/connector/storage_client.py +486 -0
  221. snowflake/connector/telemetry.py +254 -0
  222. snowflake/connector/telemetry_oob.py +544 -0
  223. snowflake/connector/test_util.py +30 -0
  224. snowflake/connector/time_util.py +159 -0
  225. snowflake/connector/token_cache.py +457 -0
  226. snowflake/connector/tool/__init__.py +0 -0
  227. snowflake/connector/tool/dump_certs.py +57 -0
  228. snowflake/connector/tool/dump_ocsp_response.py +139 -0
  229. snowflake/connector/tool/dump_ocsp_response_cache.py +194 -0
  230. snowflake/connector/tool/probe_connection.py +69 -0
  231. snowflake/connector/url_util.py +55 -0
  232. snowflake/connector/util_text.py +320 -0
  233. snowflake/connector/vendored/__init__.py +3 -0
  234. snowflake/connector/vendored/requests/LICENSE +175 -0
  235. snowflake/connector/vendored/requests/__init__.py +184 -0
  236. snowflake/connector/vendored/requests/__version__.py +14 -0
  237. snowflake/connector/vendored/requests/_internal_utils.py +50 -0
  238. snowflake/connector/vendored/requests/adapters.py +696 -0
  239. snowflake/connector/vendored/requests/api.py +157 -0
  240. snowflake/connector/vendored/requests/auth.py +314 -0
  241. snowflake/connector/vendored/requests/certs.py +17 -0
  242. snowflake/connector/vendored/requests/compat.py +106 -0
  243. snowflake/connector/vendored/requests/cookies.py +561 -0
  244. snowflake/connector/vendored/requests/exceptions.py +151 -0
  245. snowflake/connector/vendored/requests/help.py +134 -0
  246. snowflake/connector/vendored/requests/hooks.py +33 -0
  247. snowflake/connector/vendored/requests/models.py +1039 -0
  248. snowflake/connector/vendored/requests/packages.py +23 -0
  249. snowflake/connector/vendored/requests/sessions.py +831 -0
  250. snowflake/connector/vendored/requests/status_codes.py +128 -0
  251. snowflake/connector/vendored/requests/structures.py +99 -0
  252. snowflake/connector/vendored/requests/utils.py +1086 -0
  253. snowflake/connector/vendored/urllib3/LICENSE.txt +21 -0
  254. snowflake/connector/vendored/urllib3/__init__.py +211 -0
  255. snowflake/connector/vendored/urllib3/_base_connection.py +165 -0
  256. snowflake/connector/vendored/urllib3/_collections.py +487 -0
  257. snowflake/connector/vendored/urllib3/_request_methods.py +278 -0
  258. snowflake/connector/vendored/urllib3/_version.py +34 -0
  259. snowflake/connector/vendored/urllib3/connection.py +1108 -0
  260. snowflake/connector/vendored/urllib3/connectionpool.py +1178 -0
  261. snowflake/connector/vendored/urllib3/contrib/__init__.py +0 -0
  262. snowflake/connector/vendored/urllib3/contrib/emscripten/__init__.py +17 -0
  263. snowflake/connector/vendored/urllib3/contrib/emscripten/connection.py +260 -0
  264. snowflake/connector/vendored/urllib3/contrib/emscripten/emscripten_fetch_worker.js +110 -0
  265. snowflake/connector/vendored/urllib3/contrib/emscripten/fetch.py +726 -0
  266. snowflake/connector/vendored/urllib3/contrib/emscripten/request.py +22 -0
  267. snowflake/connector/vendored/urllib3/contrib/emscripten/response.py +277 -0
  268. snowflake/connector/vendored/urllib3/contrib/pyopenssl.py +564 -0
  269. snowflake/connector/vendored/urllib3/contrib/socks.py +228 -0
  270. snowflake/connector/vendored/urllib3/exceptions.py +335 -0
  271. snowflake/connector/vendored/urllib3/fields.py +341 -0
  272. snowflake/connector/vendored/urllib3/filepost.py +89 -0
  273. snowflake/connector/vendored/urllib3/http2/__init__.py +53 -0
  274. snowflake/connector/vendored/urllib3/http2/connection.py +356 -0
  275. snowflake/connector/vendored/urllib3/http2/probe.py +87 -0
  276. snowflake/connector/vendored/urllib3/poolmanager.py +651 -0
  277. snowflake/connector/vendored/urllib3/py.typed +2 -0
  278. snowflake/connector/vendored/urllib3/response.py +1480 -0
  279. snowflake/connector/vendored/urllib3/util/__init__.py +42 -0
  280. snowflake/connector/vendored/urllib3/util/connection.py +137 -0
  281. snowflake/connector/vendored/urllib3/util/proxy.py +43 -0
  282. snowflake/connector/vendored/urllib3/util/request.py +263 -0
  283. snowflake/connector/vendored/urllib3/util/response.py +101 -0
  284. snowflake/connector/vendored/urllib3/util/retry.py +549 -0
  285. snowflake/connector/vendored/urllib3/util/ssl_.py +527 -0
  286. snowflake/connector/vendored/urllib3/util/ssl_match_hostname.py +159 -0
  287. snowflake/connector/vendored/urllib3/util/ssltransport.py +271 -0
  288. snowflake/connector/vendored/urllib3/util/timeout.py +275 -0
  289. snowflake/connector/vendored/urllib3/util/url.py +469 -0
  290. snowflake/connector/vendored/urllib3/util/util.py +42 -0
  291. snowflake/connector/vendored/urllib3/util/wait.py +124 -0
  292. snowflake/connector/version.py +3 -0
  293. snowflake/connector/wif_util.py +434 -0
  294. snowflake_connector_python-4.4.0.dist-info/DELVEWHEEL +2 -0
  295. snowflake_connector_python-4.4.0.dist-info/METADATA +1591 -0
  296. snowflake_connector_python-4.4.0.dist-info/RECORD +302 -0
  297. snowflake_connector_python-4.4.0.dist-info/WHEEL +5 -0
  298. snowflake_connector_python-4.4.0.dist-info/entry_points.txt +4 -0
  299. snowflake_connector_python-4.4.0.dist-info/licenses/LICENSE.txt +202 -0
  300. snowflake_connector_python-4.4.0.dist-info/licenses/NOTICE +8 -0
  301. snowflake_connector_python-4.4.0.dist-info/top_level.txt +1 -0
  302. snowflake_connector_python.libs/msvcp140-0f885b509a685d2bbfa652fed26b5fb3.dll +0 -0
@@ -0,0 +1,734 @@
1
+ #!/usr/bin/env python
2
+ from __future__ import annotations
3
+
4
+ import atexit
5
+ import hashlib
6
+ import logging
7
+ import os
8
+ import platform
9
+ import stat
10
+ import threading
11
+ from abc import ABC, abstractmethod
12
+ from dataclasses import dataclass
13
+ from datetime import datetime, timedelta, timezone
14
+ from pathlib import Path
15
+
16
+ from cryptography import x509
17
+ from cryptography.hazmat.backends import default_backend
18
+ from cryptography.hazmat.primitives import serialization
19
+ from filelock import BaseFileLock, FileLock
20
+
21
+ from .compat import IS_WINDOWS
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ @dataclass
27
+ class CRLCacheEntry:
28
+ """Cache entry containing a CRL and its download timestamp."""
29
+
30
+ crl: x509.CertificateRevocationList
31
+ download_time: datetime
32
+
33
+ def _next_update(self) -> datetime | None:
34
+ """A compatibility wrapper around crl.next_update."""
35
+ return getattr(self.crl, "next_update_utc", None) or getattr(
36
+ self.crl, "next_update", None
37
+ )
38
+
39
+ def is_crl_expired_by(self, ts: datetime) -> bool:
40
+ """
41
+ Check if the CRL has expired.
42
+
43
+ Args:
44
+ ts: Time to check against
45
+
46
+ Returns:
47
+ True if the CRL has expired, False otherwise
48
+ """
49
+ next_update = self._next_update()
50
+ return next_update is not None and next_update < ts
51
+
52
+ def is_evicted_by(self, ts: datetime, cache_validity_time: timedelta) -> bool:
53
+ """
54
+ Check if the cache entry should be evicted based on cache validity time.
55
+
56
+ Args:
57
+ ts: Current time to check against
58
+ cache_validity_time: How long cache entries remain valid
59
+
60
+ Returns:
61
+ True if the entry should be evicted, False otherwise
62
+ """
63
+ expiry_time = self.download_time + cache_validity_time
64
+ return expiry_time < ts
65
+
66
+
67
+ class CRLCache(ABC):
68
+ """
69
+ Abstract base class for CRL caches.
70
+ """
71
+
72
+ @abstractmethod
73
+ def get(self, crl_url: str) -> CRLCacheEntry | None:
74
+ """
75
+ Get a CRL cache entry by URL.
76
+
77
+ Args:
78
+ crl_url: The CRL URL
79
+
80
+ Returns:
81
+ The cache entry if found, None otherwise
82
+ """
83
+ raise NotImplementedError()
84
+
85
+ @abstractmethod
86
+ def put(self, crl_url: str, entry: CRLCacheEntry) -> None:
87
+ """
88
+ Store a CRL cache entry.
89
+
90
+ Args:
91
+ crl_url: The CRL URL
92
+ entry: The cache entry to store
93
+ """
94
+ raise NotImplementedError()
95
+
96
+ @abstractmethod
97
+ def cleanup(self) -> None:
98
+ """Remove expired and evicted entries from the cache."""
99
+ raise NotImplementedError()
100
+
101
+
102
+ class NoopCRLCache(CRLCache):
103
+ """
104
+ No-operation CRL cache that doesn't store anything.
105
+ """
106
+
107
+ # Singleton instance
108
+ INSTANCE = None
109
+
110
+ def __new__(cls):
111
+ if cls.INSTANCE is None:
112
+ cls.INSTANCE = super().__new__(cls)
113
+ return cls.INSTANCE
114
+
115
+ def get(self, crl_url: str) -> CRLCacheEntry | None:
116
+ """Always returns None."""
117
+ return None
118
+
119
+ def put(self, crl_url: str, entry: CRLCacheEntry) -> None:
120
+ """Does nothing."""
121
+ pass
122
+
123
+ def cleanup(self) -> None:
124
+ """Does nothing."""
125
+ pass
126
+
127
+
128
+ class CRLInMemoryCache(CRLCache):
129
+ """
130
+ In-memory CRL cache using a thread-safe dictionary.
131
+ """
132
+
133
+ def __init__(self, cache_validity_time: timedelta):
134
+ """
135
+ Initialize the in-memory cache.
136
+
137
+ Args:
138
+ cache_validity_time: How long cache entries remain valid
139
+ """
140
+ self._cache: dict[str, CRLCacheEntry] = {}
141
+ self._cache_validity_time = cache_validity_time
142
+ self._lock = threading.RLock()
143
+
144
+ def get(self, crl_url: str) -> CRLCacheEntry | None:
145
+ """
146
+ Get a CRL cache entry from memory.
147
+
148
+ Args:
149
+ crl_url: The CRL URL
150
+
151
+ Returns:
152
+ The cache entry if found, None otherwise
153
+ """
154
+ with self._lock:
155
+ entry = self._cache.get(crl_url)
156
+ if entry is not None:
157
+ logger.debug(f"Found CRL in memory cache for {crl_url}")
158
+ return entry
159
+
160
+ def put(self, crl_url: str, entry: CRLCacheEntry) -> None:
161
+ """
162
+ Store a CRL cache entry in memory.
163
+
164
+ Args:
165
+ crl_url: The CRL URL
166
+ entry: The cache entry to store
167
+ """
168
+ with self._lock:
169
+ self._cache[crl_url] = entry
170
+
171
+ def cleanup(self) -> None:
172
+ """Remove expired and evicted entries from memory cache."""
173
+ now = datetime.now(timezone.utc)
174
+ logger.debug(f"Cleaning up in-memory CRL cache at {now}")
175
+
176
+ with self._lock:
177
+ urls_to_remove = []
178
+
179
+ for url, entry in self._cache.items():
180
+ expired = entry.is_crl_expired_by(now)
181
+ evicted = entry.is_evicted_by(now, self._cache_validity_time)
182
+
183
+ if expired or evicted:
184
+ logger.debug(
185
+ f"Removing in-memory CRL cache entry for {url}: "
186
+ f"expired={expired}, evicted={evicted}"
187
+ )
188
+ urls_to_remove.append(url)
189
+
190
+ for url in urls_to_remove:
191
+ del self._cache[url]
192
+
193
+ removed_count = len(urls_to_remove)
194
+ if removed_count > 0:
195
+ logger.debug(
196
+ f"Removed {removed_count} expired/evicted entries from in-memory CRL cache"
197
+ )
198
+
199
+
200
+ class CRLFileCache(CRLCache):
201
+ """
202
+ File-based CRL cache that persists CRLs to disk.
203
+ """
204
+
205
+ def __init__(
206
+ self,
207
+ cache_dir: Path | None = None,
208
+ removal_delay: timedelta | None = None,
209
+ unsafe_skip_file_permissions_check: bool = False,
210
+ ):
211
+ """
212
+ Initialize the file cache.
213
+
214
+ Args:
215
+ cache_dir: Directory to store cached CRLs
216
+ removal_delay: How long to wait before removing expired files
217
+ unsafe_skip_file_permissions_check: Skip file permission validation for security
218
+
219
+ Raises:
220
+ OSError: If cache directory cannot be created
221
+ """
222
+ self._cache_file_lock_timeout = 5.0
223
+ self._cache_dir = cache_dir or _get_default_crl_cache_path()
224
+ self._removal_delay = removal_delay or timedelta(days=7)
225
+ self._unsafe_skip_file_permissions_check = unsafe_skip_file_permissions_check
226
+
227
+ self._ensure_cache_directory_exists()
228
+
229
+ def _ensure_cache_directory_exists(self) -> None:
230
+ """Create the cache directory if it doesn't exist with secure permissions."""
231
+ try:
232
+ # Create directory with secure permissions (owner read/write/execute only)
233
+ self._cache_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
234
+
235
+ # Verify directory permissions (if it already existed)
236
+ if not self._unsafe_skip_file_permissions_check:
237
+ self._check_permissions(self._cache_dir, "directory", "0o700")
238
+
239
+ logger.debug(f"Cache directory created/verified: {self._cache_dir}")
240
+ except PermissionError:
241
+ # Re-raise permission errors as-is
242
+ raise
243
+ except OSError as e:
244
+ raise OSError(f"Failed to create cache directory {self._cache_dir}: {e}")
245
+
246
+ def _get_crl_file_path(self, crl_url: str) -> Path:
247
+ """
248
+ Generate a file path for the given CRL URL.
249
+
250
+ Args:
251
+ crl_url: The CRL URL
252
+
253
+ Returns:
254
+ Path to the cache file
255
+ """
256
+ # Create a safe filename from the URL using a hash
257
+ url_hash = hashlib.sha256(crl_url.encode()).hexdigest()
258
+ return self._cache_dir / f"crl_{url_hash}.der"
259
+
260
+ def _get_crl_file_lock(self, crl_cache_file: Path) -> BaseFileLock:
261
+ """Return a lock instance for the given CRL cache file"""
262
+ return FileLock(
263
+ crl_cache_file.with_suffix(".lock"),
264
+ thread_local=True,
265
+ timeout=self._cache_file_lock_timeout,
266
+ )
267
+
268
+ def _check_permissions(
269
+ self, path: Path, resource_type: str, expected_perms: str
270
+ ) -> None:
271
+ """
272
+ Check that a CRL cache resource has secure permissions (owner-only access).
273
+
274
+ Note: This check is only performed on Unix-like systems. Windows file
275
+ permissions work differently and are not checked.
276
+
277
+ Args:
278
+ path: Path to the resource (file or directory) to check
279
+ resource_type: Description of the resource type (e.g., "file", "directory")
280
+ expected_perms: Description of expected permissions (e.g., "0o600 or 0o400", "0o700")
281
+
282
+ Raises:
283
+ PermissionError: If resource permissions are too wide
284
+ """
285
+ # Skip permission checks on Windows as they work differently
286
+ if IS_WINDOWS:
287
+ return
288
+
289
+ try:
290
+ stat_info = path.stat()
291
+ actual_permissions = stat.S_IMODE(stat_info.st_mode)
292
+
293
+ # Check that resource is accessible only by owner (no group/other permissions)
294
+ if (
295
+ actual_permissions & 0o077 != 0
296
+ ): # Check if group or others have any permission
297
+ raise PermissionError(
298
+ f"CRL cache {resource_type} {path} has insecure permissions: {oct(actual_permissions)}. "
299
+ f"{resource_type.capitalize()} must be accessible only by the owner ({expected_perms})."
300
+ )
301
+
302
+ except FileNotFoundError:
303
+ # Resource doesn't exist yet, this is fine
304
+ pass
305
+
306
+ def get(self, crl_url: str) -> CRLCacheEntry | None:
307
+ """
308
+ Get a CRL cache entry from disk.
309
+
310
+ Args:
311
+ crl_url: The CRL URL
312
+
313
+ Returns:
314
+ The cache entry if found, None otherwise
315
+ """
316
+ crl_file_path = self._get_crl_file_path(crl_url)
317
+ with self._get_crl_file_lock(crl_file_path):
318
+ try:
319
+ if crl_file_path.exists():
320
+ logger.debug(f"Found CRL on disk for {crl_file_path}")
321
+
322
+ # Check file permissions before reading
323
+ if not self._unsafe_skip_file_permissions_check:
324
+ self._check_permissions(crl_file_path, "file", "0o600 or 0o400")
325
+ else:
326
+ logger.warning(
327
+ f"Skipping file permissions check for {crl_file_path}"
328
+ )
329
+
330
+ # Get file modification time as download time
331
+ stat_info = crl_file_path.stat()
332
+ download_time = datetime.fromtimestamp(
333
+ stat_info.st_mtime, tz=timezone.utc
334
+ )
335
+
336
+ # Read and parse the CRL
337
+ with open(crl_file_path, "rb") as f:
338
+ crl_data = f.read()
339
+
340
+ crl = x509.load_der_x509_crl(crl_data, backend=default_backend())
341
+ return CRLCacheEntry(crl, download_time)
342
+
343
+ except PermissionError as e:
344
+ logger.error(
345
+ f"Permission error reading CRL from disk cache for {crl_url}: {e}"
346
+ )
347
+ return None
348
+ except Exception as e:
349
+ logger.warning(f"Failed to read CRL from disk cache for {crl_url}: {e}")
350
+
351
+ return None
352
+
353
+ def put(self, crl_url: str, entry: CRLCacheEntry) -> None:
354
+ """
355
+ Store a CRL cache entry to disk.
356
+
357
+ Args:
358
+ crl_url: The CRL URL
359
+ entry: The cache entry to store
360
+ """
361
+ crl_file_path = self._get_crl_file_path(crl_url)
362
+ with self._get_crl_file_lock(crl_file_path):
363
+ try:
364
+ # Serialize the CRL to DER format
365
+ crl_data = entry.crl.public_bytes(serialization.Encoding.DER)
366
+
367
+ # Write to file with secure permissions (owner read/write only)
368
+ # Using os.open with 0o600 ensures the file is created with secure permissions
369
+ flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
370
+ if IS_WINDOWS:
371
+ # flag necessary for writing binary data to file on Windows OS
372
+ flags |= os.O_BINARY
373
+ fd = os.open(crl_file_path, flags, 0o600)
374
+ try:
375
+ os.write(fd, crl_data)
376
+ finally:
377
+ os.close(fd)
378
+
379
+ # Set file modification time to download time
380
+ download_timestamp = entry.download_time.timestamp()
381
+ os.utime(crl_file_path, (download_timestamp, download_timestamp))
382
+
383
+ logger.debug(f"Stored CRL to disk cache: {crl_file_path}")
384
+
385
+ except Exception as e:
386
+ logger.warning(f"Failed to write CRL to disk cache for {crl_url}: {e}")
387
+
388
+ def _is_cached_crl_file_for_removal(
389
+ self, crl_cache_file: Path, ts: datetime
390
+ ) -> bool:
391
+ """Check if the given CRL cache file is by its lifetime."""
392
+ try:
393
+ # Get file modification time
394
+ stat_info = crl_cache_file.stat()
395
+ download_time = datetime.fromtimestamp(stat_info.st_mtime, tz=timezone.utc)
396
+
397
+ # Check if file should be removed based on removal delay
398
+ removal_time = download_time + self._removal_delay
399
+ return ts > removal_time
400
+ except Exception as e:
401
+ logger.warning(f"Error processing cache file {crl_cache_file}: {e}")
402
+ return False
403
+
404
+ def cleanup(self) -> None:
405
+ """Remove expired files from disk cache."""
406
+ now = datetime.now(timezone.utc)
407
+ logger.debug(f"Cleaning up file-based CRL cache at {now}")
408
+
409
+ removed_count = 0
410
+ try:
411
+ for crl_file in self._cache_dir.glob("crl_*.der"):
412
+ # double-checked locking
413
+ if self._is_cached_crl_file_for_removal(crl_file, now):
414
+ with self._get_crl_file_lock(crl_file):
415
+ if self._is_cached_crl_file_for_removal(crl_file, now):
416
+ crl_file.unlink(missing_ok=True)
417
+ removed_count += 1
418
+ logger.debug(f"Removed expired file: {crl_file}")
419
+ except Exception as e:
420
+ logger.error(f"Error during file cache cleanup: {e}")
421
+
422
+
423
+ class CRLCacheManager:
424
+ """
425
+ Cache manager that coordinates between in-memory and file-based CRL caches.
426
+ """
427
+
428
+ def __init__(
429
+ self,
430
+ memory_cache: CRLCache,
431
+ file_cache: CRLCache,
432
+ ):
433
+ """
434
+ Initialize the cache manager.
435
+
436
+ Args:
437
+ memory_cache: In-memory cache implementation
438
+ file_cache: File-based cache implementation
439
+ """
440
+ self._memory_cache = memory_cache
441
+ self._file_cache = file_cache
442
+
443
+ @classmethod
444
+ def noop(cls) -> CRLCacheManager:
445
+ """Create noop cache manager."""
446
+ return cls(NoopCRLCache(), NoopCRLCache())
447
+
448
+ def get(self, crl_url: str) -> CRLCacheEntry | None:
449
+ """
450
+ Get a CRL cache entry, checking memory cache first, then file cache.
451
+
452
+ Args:
453
+ crl_url: The CRL URL
454
+
455
+ Returns:
456
+ The cache entry if found, None otherwise
457
+ """
458
+ # Check memory cache first
459
+ entry = self._memory_cache.get(crl_url)
460
+ if entry is not None:
461
+ return entry
462
+
463
+ # Check file cache
464
+ entry = self._file_cache.get(crl_url)
465
+ if entry is not None:
466
+ # Promote to memory cache
467
+ self._memory_cache.put(crl_url, entry)
468
+ return entry
469
+
470
+ logger.debug(f"CRL not found in cache for {crl_url}")
471
+ return None
472
+
473
+ def put(
474
+ self, crl_url: str, crl: x509.CertificateRevocationList, download_time: datetime
475
+ ) -> None:
476
+ """
477
+ Store a CRL in both memory and file caches.
478
+
479
+ Args:
480
+ crl_url: The CRL URL
481
+ crl: The CRL to store
482
+ download_time: When the CRL was downloaded
483
+ """
484
+ entry = CRLCacheEntry(crl, download_time)
485
+ self._memory_cache.put(crl_url, entry)
486
+ self._file_cache.put(crl_url, entry)
487
+
488
+
489
+ class CRLCacheFactory:
490
+ """
491
+ Factory class for creating singleton instances of CRL caches.
492
+
493
+ This factory ensures that only one instance of each cache type exists,
494
+ providing warnings when attempting to create instances with different parameters.
495
+ Also manages background cleanup of existing cache instances.
496
+ """
497
+
498
+ # Singleton instances
499
+ _memory_cache_instance = None
500
+ _file_cache_instance = None
501
+ _instance_lock = threading.RLock()
502
+
503
+ # Cleanup management
504
+ _cleanup_thread: threading.Thread | None = None
505
+ _cleanup_shutdown: threading.Event = threading.Event()
506
+ _cleanup_interval: timedelta | None = None
507
+ _atexit_registered: bool = False
508
+
509
+ @classmethod
510
+ def get_memory_cache(cls, cache_validity_time: timedelta) -> CRLInMemoryCache:
511
+ """
512
+ Get or create a singleton CRLInMemoryCache instance.
513
+
514
+ Args:
515
+ cache_validity_time: How long cache entries remain valid
516
+
517
+ Returns:
518
+ The singleton CRLInMemoryCache instance
519
+ """
520
+ with cls._instance_lock:
521
+ if cls._memory_cache_instance is None:
522
+ cls._memory_cache_instance = CRLInMemoryCache(cache_validity_time)
523
+ elif cls._memory_cache_instance._cache_validity_time != cache_validity_time:
524
+ logger.warning(
525
+ f"CRLs in-memory cache has already been initialized with cache validity time of {cls._memory_cache_instance._cache_validity_time}, "
526
+ f"ignoring new cache validity time of {cache_validity_time}"
527
+ )
528
+ return cls._memory_cache_instance
529
+
530
+ @classmethod
531
+ def get_file_cache(
532
+ cls,
533
+ cache_dir: Path | None = None,
534
+ removal_delay: timedelta | None = None,
535
+ unsafe_skip_file_permissions_check: bool = False,
536
+ ) -> CRLFileCache:
537
+ """
538
+ Get or create a singleton CRLFileCache instance.
539
+
540
+ Args:
541
+ cache_dir: Directory to store cached CRLs
542
+ removal_delay: How long to wait before removing expired files
543
+ unsafe_skip_file_permissions_check: Skip file permission validation for security
544
+
545
+ Returns:
546
+ The singleton CRLFileCache instance
547
+ """
548
+ with cls._instance_lock:
549
+ if cls._file_cache_instance is None:
550
+ cls._file_cache_instance = CRLFileCache(
551
+ cache_dir, removal_delay, unsafe_skip_file_permissions_check
552
+ )
553
+ else:
554
+ # Check if parameters differ from existing instance
555
+ existing_cache_dir = cls._file_cache_instance._cache_dir
556
+ existing_removal_delay = cls._file_cache_instance._removal_delay
557
+ existing_skip_check = (
558
+ cls._file_cache_instance._unsafe_skip_file_permissions_check
559
+ )
560
+ requested_cache_dir = cache_dir or _get_default_crl_cache_path()
561
+ requested_removal_delay = removal_delay or timedelta(days=7)
562
+
563
+ if existing_cache_dir != requested_cache_dir:
564
+ logger.warning(
565
+ f"CRLs file cache has already been initialized with cache directory '{existing_cache_dir}', "
566
+ f"ignoring new cache directory '{requested_cache_dir}'"
567
+ )
568
+ if existing_removal_delay != requested_removal_delay:
569
+ logger.warning(
570
+ f"CRLs file cache has already been initialized with removal delay of {existing_removal_delay}, "
571
+ f"ignoring new removal delay of {requested_removal_delay}"
572
+ )
573
+ if existing_skip_check != unsafe_skip_file_permissions_check:
574
+ logger.warning(
575
+ f"CRLs file cache has already been initialized with unsafe_skip_file_permissions_check={existing_skip_check}, "
576
+ f"ignoring new value {unsafe_skip_file_permissions_check}"
577
+ )
578
+ return cls._file_cache_instance
579
+
580
+ @classmethod
581
+ def start_periodic_cleanup(cls, cleanup_interval: timedelta) -> None:
582
+ """
583
+ Start the periodic cleanup task for existing cache instances.
584
+
585
+ Args:
586
+ cleanup_interval: How often to run cleanup tasks
587
+ """
588
+ with cls._instance_lock:
589
+ if cls.is_periodic_cleanup_running():
590
+ logger.debug(
591
+ "Periodic cleanup already running, so it will first be stopped before restarting."
592
+ )
593
+ cls.stop_periodic_cleanup()
594
+
595
+ cls._cleanup_interval = cleanup_interval
596
+ cls._cleanup_thread = threading.Thread(
597
+ target=cls._cleanup_loop,
598
+ name="crl-cache-cleanup",
599
+ daemon=True, # Make it a daemon thread so it doesn't block program exit
600
+ )
601
+
602
+ # Register atexit handler for graceful shutdown (only once)
603
+ if not cls._atexit_registered:
604
+ atexit.register(cls._atexit_cleanup_handler)
605
+ cls._atexit_registered = True
606
+
607
+ # Start the cleanup thread
608
+ cls._cleanup_thread.start()
609
+
610
+ logger.debug(
611
+ f"Scheduled CRL cache cleanup task to run every {cleanup_interval.total_seconds()} seconds."
612
+ )
613
+
614
+ @classmethod
615
+ def stop_periodic_cleanup(cls) -> None:
616
+ """Stop the periodic cleanup task."""
617
+ thread_to_join = None
618
+
619
+ with cls._instance_lock:
620
+ if cls._cleanup_thread is None or cls._cleanup_shutdown.is_set():
621
+ return
622
+
623
+ cls._cleanup_shutdown.set()
624
+ thread_to_join = cls._cleanup_thread
625
+
626
+ # Join thread outside of lock to avoid deadlock
627
+ if thread_to_join is not None and thread_to_join.is_alive():
628
+ thread_to_join.join(timeout=5.0)
629
+
630
+ with cls._instance_lock:
631
+ cls._cleanup_shutdown.clear()
632
+ cls._cleanup_thread = None
633
+ cls._cleanup_interval = None
634
+
635
+ @classmethod
636
+ def is_periodic_cleanup_running(cls) -> bool:
637
+ """Check if periodic cleanup task is running."""
638
+ with cls._instance_lock:
639
+ return cls._cleanup_thread is not None and cls._cleanup_thread.is_alive()
640
+
641
+ @classmethod
642
+ def _cleanup_loop(cls) -> None:
643
+ """Main cleanup loop that runs periodically."""
644
+ while not cls._cleanup_shutdown.is_set():
645
+ if cls._cleanup_interval is None:
646
+ break
647
+
648
+ logger.debug(
649
+ f"Running periodic CRL cache cleanup with interval {cls._cleanup_interval.total_seconds()} seconds"
650
+ )
651
+
652
+ # Clean memory cache only if it exists
653
+ if cls._memory_cache_instance is not None:
654
+ try:
655
+ cls._memory_cache_instance.cleanup()
656
+ except Exception as e:
657
+ logger.error(
658
+ f"An error occurred during scheduled CRL memory cache cleanup: {e}"
659
+ )
660
+
661
+ # Clean file cache only if it exists
662
+ if cls._file_cache_instance is not None:
663
+ try:
664
+ cls._file_cache_instance.cleanup()
665
+ except Exception as e:
666
+ logger.error(
667
+ f"An error occurred during scheduled CRL disk cache cleanup: {e}"
668
+ )
669
+
670
+ shutdown = cls._cleanup_shutdown.wait(
671
+ timeout=cls._cleanup_interval.total_seconds()
672
+ )
673
+ if shutdown:
674
+ logger.debug(
675
+ "CRL cache cleanup stopped gracefully by a shutdown event."
676
+ )
677
+ break
678
+
679
+ @classmethod
680
+ def _atexit_cleanup_handler(cls) -> None:
681
+ """
682
+ Atexit handler to ensure graceful shutdown of periodic cleanup on program exit.
683
+ """
684
+ try:
685
+ cls.stop_periodic_cleanup()
686
+ logger.debug("CRL cache cleanup stopped gracefully on program exit.")
687
+ except Exception as e:
688
+ # Don't raise exceptions in atexit handlers
689
+ logger.error(f"Error stopping CRL cache cleanup on program exit: {e}")
690
+
691
+ @classmethod
692
+ def reset(cls) -> None:
693
+ """
694
+ Reset the factory, clearing all singleton instances and stopping cleanup.
695
+ This is primarily useful for testing purposes.
696
+ """
697
+ with cls._instance_lock:
698
+ cls.stop_periodic_cleanup()
699
+ cls._memory_cache_instance = None
700
+ cls._file_cache_instance = None
701
+ cls._atexit_registered = False
702
+
703
+
704
+ def _get_windows_home_path() -> Path:
705
+ try:
706
+ return Path.home()
707
+ except RuntimeError:
708
+ pass
709
+ if "USERPROFILE" in os.environ:
710
+ return Path(os.environ["USERPROFILE"])
711
+ if "HOMEDRIVE" in os.environ and "HOMEPATH" in os.environ:
712
+ return Path(os.environ["HOMEDRIVE"]) / os.environ["HOMEPATH"]
713
+ if "LOCALAPPDATA" in os.environ:
714
+ return Path(os.environ["LOCALAPPDATA"]).parent.parent
715
+ if "APPDATA" in os.environ:
716
+ return Path(os.environ["APPDATA"]).parent.parent
717
+ return Path("~")
718
+
719
+
720
+ def _get_default_crl_cache_path() -> Path:
721
+ """Return the default path to persist cached CRLs."""
722
+ if platform.system() == "Windows":
723
+ return (
724
+ _get_windows_home_path()
725
+ / "AppData"
726
+ / "Local"
727
+ / "Snowflake"
728
+ / "Caches"
729
+ / "crls"
730
+ )
731
+ elif platform.system() == "Darwin":
732
+ return Path.home() / "Library" / "Caches" / "Snowflake" / "crls"
733
+ else:
734
+ return Path.home() / ".cache" / "Snowflake" / "crls"