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

beto pushed a commit to branch sl-3-api-integration
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 20d1480db50e199aae87d1cd589ca200430a22e7
Author: Beto Dealmeida <[email protected]>
AuthorDate: Mon Feb 2 17:29:51 2026 -0500

    feat(semantic-layer): integrate semantic views with explore API
    
    Update views and APIs to support semantic layer explorables:
    - Add UUID support for datasource IDs in views and utils
    - Update explore command to handle semantic views
    - Extend datasource DAO to resolve semantic view UUIDs
    - Update explore API parameters for semantic layer support
    
    Co-Authored-By: Claude Opus 4.5 <[email protected]>
---
 superset/commands/explore/get.py        | 15 ++++++++++-----
 superset/commands/explore/parameters.py |  4 ++--
 superset/daos/datasource.py             |  4 +++-
 superset/explore/api.py                 |  2 +-
 superset/views/core.py                  | 19 ++++++++++---------
 superset/views/utils.py                 | 23 +++++++++++++++--------
 6 files changed, 41 insertions(+), 26 deletions(-)

diff --git a/superset/commands/explore/get.py b/superset/commands/explore/get.py
index 78142eb5ec1..7a99d5a2a42 100644
--- a/superset/commands/explore/get.py
+++ b/superset/commands/explore/get.py
@@ -18,6 +18,7 @@ import contextlib
 import logging
 from abc import ABC
 from typing import Any, cast, Optional
+from uuid import UUID
 
 from flask import request
 from flask_babel import lazy_gettext as _
@@ -100,21 +101,21 @@ class GetExploreCommand(BaseCommand, ABC):
             use_slice_data=True,
             initial_form_data=initial_form_data,
         )
+        ds_id: int | UUID | None = None
         try:
-            self._datasource_id, self._datasource_type = get_datasource_info(
+            ds_id, self._datasource_type = get_datasource_info(
                 self._datasource_id, self._datasource_type, form_data
             )
         except SupersetException:
-            self._datasource_id = None
             # fallback unknown datasource to table type
             self._datasource_type = SqlaTable.type
 
         datasource: Optional[BaseDatasource] = None
 
-        if self._datasource_id is not None:
+        if ds_id is not None:
             with contextlib.suppress(DatasourceNotFound):
                 datasource = DatasourceDAO.get_datasource(
-                    cast(str, self._datasource_type), self._datasource_id
+                    cast(str, self._datasource_type), ds_id
                 )
 
         datasource_name = _("[Missing Dataset]")
@@ -124,7 +125,11 @@ class GetExploreCommand(BaseCommand, ABC):
             security_manager.raise_for_access(datasource=datasource)
 
         viz_type = form_data.get("viz_type")
-        if not viz_type and datasource and datasource.default_endpoint:
+        if (
+            not viz_type
+            and datasource
+            and getattr(datasource, "default_endpoint", None)
+        ):
             raise WrongEndpointError(redirect=datasource.default_endpoint)
 
         form_data["datasource"] = (
diff --git a/superset/commands/explore/parameters.py 
b/superset/commands/explore/parameters.py
index 1aa5418d626..d5457e102e6 100644
--- a/superset/commands/explore/parameters.py
+++ b/superset/commands/explore/parameters.py
@@ -15,13 +15,13 @@
 # specific language governing permissions and limitations
 # under the License.
 from dataclasses import dataclass
-from typing import Optional
+from typing import Optional, Union
 
 
 @dataclass
 class CommandParameters:
     permalink_key: Optional[str]
     form_data_key: Optional[str]
-    datasource_id: Optional[int]
+    datasource_id: Optional[Union[int, str]]
     datasource_type: Optional[str]
     slice_id: Optional[int]
diff --git a/superset/daos/datasource.py b/superset/daos/datasource.py
index 308785f625e..f5030f35353 100644
--- a/superset/daos/datasource.py
+++ b/superset/daos/datasource.py
@@ -28,6 +28,7 @@ from superset.daos.exceptions import (
     DatasourceValueIsIncorrect,
 )
 from superset.models.sql_lab import Query, SavedQuery
+from superset.semantic_layers.models import SemanticView
 from superset.utils.core import DatasourceType
 
 logger = logging.getLogger(__name__)
@@ -40,13 +41,14 @@ class DatasourceDAO(BaseDAO[Datasource]):
         DatasourceType.TABLE: SqlaTable,
         DatasourceType.QUERY: Query,
         DatasourceType.SAVEDQUERY: SavedQuery,
+        DatasourceType.SEMANTIC_VIEW: SemanticView,
     }
 
     @classmethod
     def get_datasource(
         cls,
         datasource_type: Union[DatasourceType, str],
-        database_id_or_uuid: int | str,
+        database_id_or_uuid: int | str | uuid.UUID,
     ) -> Datasource:
         if datasource_type not in cls.sources:
             raise DatasourceTypeNotSupportedError()
diff --git a/superset/explore/api.py b/superset/explore/api.py
index e16b083feb8..a9b66d468c9 100644
--- a/superset/explore/api.py
+++ b/superset/explore/api.py
@@ -109,7 +109,7 @@ class ExploreRestApi(BaseSupersetApi):
             params = CommandParameters(
                 permalink_key=request.args.get("permalink_key", type=str),
                 form_data_key=request.args.get("form_data_key", type=str),
-                datasource_id=request.args.get("datasource_id", type=int),
+                datasource_id=request.args.get("datasource_id"),
                 datasource_type=request.args.get("datasource_type", type=str),
                 slice_id=request.args.get("slice_id", type=int),
             )
diff --git a/superset/views/core.py b/superset/views/core.py
index cbe722cc92c..fde9f21efa9 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -24,6 +24,7 @@ import re
 from datetime import datetime
 from typing import Any, Callable, cast
 from urllib import parse
+from uuid import UUID
 
 from flask import (
     abort,
@@ -268,7 +269,7 @@ class Superset(BaseSupersetView):
     @check_resource_permissions(check_datasource_perms)
     @deprecated(eol_version="5.0.0")
     def explore_json(
-        self, datasource_type: str | None = None, datasource_id: int | None = 
None
+        self, datasource_type: str | None = None, datasource_id: int | str | 
None = None
     ) -> FlaskResponse:
         """Serves all request that GET or POST form_data
 
@@ -302,7 +303,7 @@ class Superset(BaseSupersetView):
 
         form_data = get_form_data()[0]
         try:
-            datasource_id, datasource_type = get_datasource_info(
+            ds_id, datasource_type = get_datasource_info(
                 datasource_id, datasource_type, form_data
             )
             force = request.args.get("force") == "true"
@@ -316,7 +317,7 @@ class Superset(BaseSupersetView):
                 with contextlib.suppress(CacheLoadError):
                     viz_obj = get_viz(
                         datasource_type=cast(str, datasource_type),
-                        datasource_id=datasource_id,
+                        datasource_id=ds_id,
                         form_data=form_data,
                         force_cached=True,
                         force=force,
@@ -343,7 +344,7 @@ class Superset(BaseSupersetView):
 
             viz_obj = get_viz(
                 datasource_type=cast(str, datasource_type),
-                datasource_id=datasource_id,
+                datasource_id=ds_id,
                 form_data=form_data,
                 force=force,
             )
@@ -407,7 +408,7 @@ class Superset(BaseSupersetView):
     def explore(  # noqa: C901
         self,
         datasource_type: str | None = None,
-        datasource_id: int | None = None,
+        datasource_id: int | str | None = None,
         key: str | None = None,
     ) -> FlaskResponse:
         if request.method == "GET":
@@ -451,21 +452,21 @@ class Superset(BaseSupersetView):
 
         query_context = request.form.get("query_context")
 
+        ds_id: int | UUID | None = None
         try:
-            datasource_id, datasource_type = get_datasource_info(
+            ds_id, datasource_type = get_datasource_info(
                 datasource_id, datasource_type, form_data
             )
         except SupersetException:
-            datasource_id = None
             # fallback unknown datasource to table type
             datasource_type = SqlaTable.type
 
         datasource: BaseDatasource | None = None
-        if datasource_id is not None:
+        if ds_id is not None:
             with contextlib.suppress(DatasetNotFoundError):
                 datasource = DatasourceDAO.get_datasource(
                     DatasourceType("table"),
-                    datasource_id,
+                    ds_id,
                 )
 
         datasource_name = datasource.name if datasource else _("[Missing 
Dataset]")
diff --git a/superset/views/utils.py b/superset/views/utils.py
index 1ec9e2a54ac..57e79b93cf0 100644
--- a/superset/views/utils.py
+++ b/superset/views/utils.py
@@ -20,6 +20,7 @@ from collections import defaultdict
 from functools import wraps
 from typing import Any, Callable, DefaultDict, Optional, Union
 from urllib import parse
+from uuid import UUID
 
 import msgpack
 import pyarrow as pa
@@ -163,7 +164,7 @@ def get_permissions(
 def get_viz(
     form_data: FormData,
     datasource_type: str,
-    datasource_id: int,
+    datasource_id: int | UUID,
     force: bool = False,
     force_cached: bool = False,
 ) -> BaseViz:
@@ -272,8 +273,10 @@ def add_sqllab_custom_filters(form_data: dict[Any, Any]) 
-> Any:
 
 
 def get_datasource_info(
-    datasource_id: Optional[int], datasource_type: Optional[str], form_data: 
FormData
-) -> tuple[int, Optional[str]]:
+    datasource_id: int | str | None,
+    datasource_type: Optional[str],
+    form_data: FormData,
+) -> tuple[int | UUID, Optional[str]]:
     """
     Compatibility layer for handling of datasource info
 
@@ -300,8 +303,12 @@ def get_datasource_info(
             _("The dataset associated with this chart no longer exists")
         )
 
-    datasource_id = int(datasource_id)
-    return datasource_id, datasource_type
+    # Convert datasource_id to appropriate type
+    if isinstance(datasource_id, int):
+        return datasource_id, datasource_type
+    if datasource_id.isdigit():
+        return int(datasource_id), datasource_type
+    return UUID(datasource_id), datasource_type
 
 
 def apply_display_max_row_limit(
@@ -483,7 +490,7 @@ def check_explore_cache_perms(_self: Any, cache_key: str) 
-> None:
 def check_datasource_perms(
     _self: Any,
     datasource_type: Optional[str] = None,
-    datasource_id: Optional[int] = None,
+    datasource_id: int | str | None = None,
     **kwargs: Any,
 ) -> None:
     """
@@ -500,7 +507,7 @@ def check_datasource_perms(
     form_data = kwargs["form_data"] if "form_data" in kwargs else 
get_form_data()[0]
 
     try:
-        datasource_id, datasource_type = get_datasource_info(
+        ds_id, datasource_type = get_datasource_info(
             datasource_id, datasource_type, form_data
         )
     except SupersetException as ex:
@@ -524,7 +531,7 @@ def check_datasource_perms(
     try:
         viz_obj = get_viz(
             datasource_type=datasource_type,
-            datasource_id=datasource_id,
+            datasource_id=ds_id,
             form_data=form_data,
             force=False,
         )

Reply via email to