This is an automated email from the ASF dual-hosted git repository.

beto pushed a commit to branch explorable
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/explorable by this push:
     new 20477b0947 WIP
20477b0947 is described below

commit 20477b0947884b0a00bf7831a2c1132012b0d578
Author: Beto Dealmeida <[email protected]>
AuthorDate: Fri Oct 3 15:03:27 2025 -0400

    WIP
---
 superset/semantic_layers/snowflake_.py | 125 +++++++++++++++++++++------------
 1 file changed, 79 insertions(+), 46 deletions(-)

diff --git a/superset/semantic_layers/snowflake_.py 
b/superset/semantic_layers/snowflake_.py
index 9aa2743dd7..7f89dbf20b 100644
--- a/superset/semantic_layers/snowflake_.py
+++ b/superset/semantic_layers/snowflake_.py
@@ -21,8 +21,9 @@ from typing import Any, Literal, Union
 
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives import serialization
-from pydantic import BaseModel, ConfigDict, Field, SecretStr
+from pydantic import BaseModel, ConfigDict, create_model, Field, SecretStr
 from snowflake.connector import connect
+from snowflake.connector.connection import SnowflakeConnection
 
 
 class UserPasswordAuth(BaseModel):
@@ -63,8 +64,6 @@ class SnowflakeConfiguration(BaseModel):
     Parameters needed to connect to Snowflake.
     """
 
-    model_config = ConfigDict(protected_namespaces=())
-
     # account is the only required parameter
     account_identifier: str = Field(
         description="The Snowflake account identifier.",
@@ -94,8 +93,8 @@ class SnowflakeConfiguration(BaseModel):
         description="The default database to use.",
         json_schema_extra={
             "examples": ["testdb"],
-            "dynamic": True,
-            "depends_on": ["account_identifier", "auth"],
+            "x-dynamic": True,
+            "x-dependsOn": ["account_identifier", "auth"],
         },
     )
     allow_changing_database: bool = Field(
@@ -107,8 +106,8 @@ class SnowflakeConfiguration(BaseModel):
         description="The default schema to use.",
         json_schema_extra={
             "examples": ["public"],
-            "dynamic": True,
-            "depends_on": ["account_identifier", "auth", "database"],
+            "x-dynamic": True,
+            "x-dependsOn": ["account_identifier", "auth", "database"],
         },
         # `schema` is an attribute of `BaseModel` so it needs to be aliased
         alias="schema",
@@ -141,21 +140,31 @@ class SnowflakeSemanticLayer:
         explorables.
         """
         schema = cls.configuration_schema.model_json_schema()
+        properties = schema["properties"]
 
         if configuration is None:
-            return schema
-
-        for field_name, field_info in schema["properties"].items():
-            dynamic = field_info.get("dynamic", False)
-            if not dynamic:
-                continue
+            # set these to empty; they will be populated when a partial 
configuration is
+            # passed
+            properties["database"]["enum"] = []
+            properties["schema"]["enum"] = []
 
-            depends_on = field_info.get("depends_on", [])
+            return schema
 
-            # check if all deps are satisfied
-            if all(getattr(configuration, dependency) for dependency in 
depends_on):
-                enum_values = cls._fetch_enum_values(field_name, configuration)
-                field_info["enum"] = enum_values
+        connection_parameters = cls._get_connection_parameters(configuration)
+        with connect(**connection_parameters) as connection:
+            if all(
+                getattr(configuration, dependency)
+                for dependency in properties["database"].get("x-dependsOn", [])
+            ):
+                options = cls._fetch_databases(connection)
+                properties["database"]["enum"] = list(options)
+
+            if all(
+                getattr(configuration, dependency)
+                for dependency in properties["schema"].get("x-dependsOn", [])
+            ):
+                options = cls._fetch_schemas(connection, 
configuration.database)
+                properties["schema"]["enum"] = list(options)
 
         return schema
 
@@ -167,36 +176,60 @@ class SnowflakeSemanticLayer:
         """
         Get the JSON schema for the runtime parameters needed to load 
explorables.
         """
-        pass
+        fields: dict[str, tuple[type, Field]] = {}
 
-    @classmethod
-    def _fetch_enum_values(
-        cls,
-        field_name: str,
-        configuration: SnowflakeConfiguration,
-    ) -> set[Any]:
-        """
-        Fetch enum values for a given field based on the partial configuration.
-        """
         connection_parameters = cls._get_connection_parameters(configuration)
         with connect(**connection_parameters) as connection:
-            cursor = connection.cursor()
+            if not configuration.database or 
configuration.allow_changing_database:
+                options = cls._fetch_databases(connection)
+                fields["database"] = (
+                    Literal[*options],
+                    Field(description="The default database to use."),
+                )
+
+            if not configuration.schema_ or 
configuration.allow_changing_schema:
+                options = cls._fetch_schemas(connection, 
configuration.database)
+                fields["schema_"] = (
+                    Literal[*options],
+                    Field(description="The default schema to use.", 
alias="schema"),
+                )
+
+        return create_model("RuntimeParameters", **fields).model_json_schema()
 
-            if field_name == "database":
-                query = "SHOW DATABASES"
-                return {row[1] for row in cursor.execute(query)}
+    @classmethod
+    def _fetch_databases(cls, connection: SnowflakeConnection) -> set[str]:
+        """
+        Fetch the list of databases available in the Snowflake account.
 
-            if field_name == "schema":
-                query = """
-                    SELECT SCHEMA_NAME
-                    FROM INFORMATION_SCHEMA.SCHEMATA
-                    WHERE CATALOG_NAME = ?
-                """
-                cursor.execute(query, (configuration.database,))
-                return {row[0] for row in cursor.fetchall()}
+        We use `SHOW DATABASES` instead of querying the information schema 
since it
+        allows to retrieve the list of databases without having to specify a 
database
+        when connecting.
+        """
+        cursor = connection.cursor()
+        query = "SHOW DATABASES"
+        cursor.execute(query)
+        return {row[1] for row in cursor.fetchall()}
 
-        # should never happen, since only database and schema are dynamic
-        raise ValueError(f"Unsupported field for enum fetching: {field_name}")
+    @classmethod
+    def _fetch_schemas(
+        cls,
+        connection: SnowflakeConnection,
+        database: str | None,
+    ) -> set[str]:
+        """
+        Fetch the list of schemas available in a given database.
+        """
+        if not database:
+            return set()
+
+        cursor = connection.cursor()
+        query = """
+            SELECT SCHEMA_NAME
+            FROM INFORMATION_SCHEMA.SCHEMATA
+            WHERE CATALOG_NAME = ?
+        """
+        cursor.execute(query, (database,))
+        return {row[0] for row in cursor.fetchall()}
 
     @classmethod
     def _get_connection_parameters(
@@ -208,7 +241,7 @@ class SnowflakeSemanticLayer:
         """
         params = {
             "account": configuration.account_identifier,
-            "application": "Apache Superset",  # TODO: use 
superset.utils.core.get_user_agent
+            "application": "Apache Superset",
             "paramstyle": "qmark",
             "insecure_mode": True,
         }
@@ -219,8 +252,8 @@ class SnowflakeSemanticLayer:
             params["warehouse"] = configuration.warehouse
         if configuration.database:
             params["database"] = configuration.database
-        if configuration.schema:
-            params["schema"] = configuration.schema
+        if configuration.schema_:
+            params["schema"] = configuration.schema_
 
         auth = configuration.auth
         if isinstance(auth, UserPasswordAuth):
@@ -269,7 +302,7 @@ if __name__ == "__main__":
             "account_identifier": "KFTRUWN-VX32922",
             "role": "ACCOUNTADMIN",
             "warehouse": "COMPUTE_WH",
-            # "database": "SAMPLE_DATA",
+            "database": "SAMPLE_DATA",
             "auth": {
                 "auth_type": "user_password",
                 "username": "vavila",

Reply via email to