-
Notifications
You must be signed in to change notification settings - Fork 7
Switch SiS demo to OAuth session token auth #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
b205799
aff4c77
6f9132a
a770004
3dd583e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,8 @@ | ||
| type: snowflake | ||
| name: snowflake | ||
| connection: | ||
| account: {{ env_var('SNOWFLAKE_DS_ACCOUNT') }} | ||
| account: {{ env_var('SNOWFLAKE_ACCOUNT') }} | ||
| warehouse: {{ env_var('SNOWFLAKE_DS_WAREHOUSE') }} | ||
| database: {{ env_var('SNOWFLAKE_DS_DATABASE') }} | ||
| user: {{ env_var('SNOWFLAKE_DS_USER') }} | ||
| auth: | ||
| password: {{ env_var('SNOWFLAKE_DS_PASSWORD') }} | ||
| authenticator: externalbrowser |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,9 +2,16 @@ | |
| import logging | ||
| import os | ||
| import sys | ||
| from collections.abc import Generator | ||
| from contextlib import contextmanager | ||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| import snowflake.connector | ||
| import streamlit as st | ||
| from databao_context_engine.plugins.databases.snowflake.snowflake_introspector import ( | ||
| SnowflakeIntrospector, | ||
| ) | ||
|
|
||
| from databao_cli.ui.app import main | ||
|
|
||
|
|
@@ -13,18 +20,17 @@ | |
| SNOWFLAKE_SECRETS: dict[str, str] = { | ||
| "openai_api_key": "OPENAI_API_KEY", | ||
| "anthropic_api_key": "ANTHROPIC_API_KEY", | ||
| "snowflake_ds_account": "SNOWFLAKE_DS_ACCOUNT", | ||
| "snowflake_ds_warehouse": "SNOWFLAKE_DS_WAREHOUSE", | ||
| "snowflake_ds_database": "SNOWFLAKE_DS_DATABASE", | ||
| "snowflake_ds_user": "SNOWFLAKE_DS_USER", | ||
| "snowflake_ds_password": "SNOWFLAKE_DS_PASSWORD", | ||
| } | ||
|
|
||
| SESSION_TOKEN_PATH = Path("/snowflake/session/token") | ||
|
|
||
| ADBC_LIB = "libadbc_driver_snowflake.so" | ||
|
|
||
|
|
||
| def _is_running_in_snowflake() -> bool: | ||
| return Path("/snowflake/session/token").exists() | ||
| return SESSION_TOKEN_PATH.exists() | ||
|
|
||
|
|
||
| def _ensure_adbc_driver() -> None: | ||
|
|
@@ -72,9 +78,46 @@ def _load_snowflake_secrets() -> None: | |
| logger.warning("Failed to load secret '%s'", secret_name, exc_info=True) | ||
|
|
||
|
|
||
| def _patch_snowflake_introspector_for_sis() -> None: | ||
| """Monkey-patch SnowflakeIntrospector._connect for Streamlit-in-Snowflake (SiS). | ||
|
|
||
| In SiS, the runtime maintains an OAuth session token at | ||
| /snowflake/session/token. We re-read it on every connection to avoid expiry | ||
| (tokens are valid ~1 hour, the file refreshes every few minutes). | ||
|
|
||
| DCE's _connect must return a context manager because BaseIntrospector uses | ||
| ``with self._connect(file_config) as conn:``. | ||
| """ | ||
| @contextmanager | ||
| def _sis_connect(self: Any, file_config: Any, *, catalog: str | None = None) -> Generator[Any, None, None]: | ||
| token = SESSION_TOKEN_PATH.read_text().strip() | ||
| snowflake.connector.paramstyle = "qmark" | ||
| kwargs = file_config.connection.to_snowflake_kwargs() | ||
|
Comment on lines
+117
to
+118
|
||
| # Replace any existing auth params with OAuth token | ||
| kwargs.pop("password", None) | ||
| kwargs.pop("private_key", None) | ||
| kwargs.pop("private_key_file", None) | ||
| kwargs.pop("private_key_file_pwd", None) | ||
| kwargs.pop("authenticator", None) | ||
| kwargs.pop("token", None) | ||
| kwargs["authenticator"] = "oauth" | ||
| kwargs["token"] = token | ||
| if catalog: | ||
| kwargs["database"] = catalog | ||
| conn = snowflake.connector.connect(**kwargs) | ||
| try: | ||
| yield conn | ||
| finally: | ||
| conn.close() | ||
|
|
||
| SnowflakeIntrospector._connect = _sis_connect # type: ignore[assignment] | ||
| logger.info("Patched SnowflakeIntrospector._connect for SiS OAuth token auth") | ||
|
Comment on lines
+139
to
+140
|
||
|
|
||
|
|
||
| _ensure_adbc_driver() | ||
| if _is_running_in_snowflake(): | ||
| _load_snowflake_secrets() | ||
| _patch_snowflake_introspector_for_sis() | ||
|
|
||
| if "--project-dir" not in sys.argv: | ||
| sys.argv.extend(["--project-dir", "examples/demo-snowflake-project"]) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SESSION_TOKEN_PATH.read_text().strip()can raise (e.g., transient file-not-found/permission issues) and can also produce an empty token; either case will currently bubble up as a low-level exception or a confusing connector auth failure. Consider catchingOSError/decode errors, validating the token is non-empty, and raising/logging a clear message that the SiS OAuth session token could not be read from/snowflake/session/token.