Compare commits

..

1 Commits

Author SHA1 Message Date
Beto Dealmeida
37094dc404 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 <noreply@anthropic.com>
2026-02-03 21:59:18 -05:00
6 changed files with 41 additions and 26 deletions

View File

@@ -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"] = (

View File

@@ -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]

View File

@@ -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()

View File

@@ -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),
)

View File

@@ -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]")

View File

@@ -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,
)