This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch semantic-layer-feature
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/semantic-layer-feature by this
push:
new cd8e27d33c9 API integration
cd8e27d33c9 is described below
commit cd8e27d33c909f56edb3b97dc13adcdcd13b1640
Author: Beto Dealmeida <[email protected]>
AuthorDate: Fri Feb 6 16:28:08 2026 -0500
API integration
---
superset/commands/explore/get.py | 19 +++++++++---
superset/commands/explore/parameters.py | 11 ++++---
superset/daos/datasource.py | 8 +++--
superset/explore/api.py | 2 +-
superset/views/core.py | 35 ++++++++++++---------
superset/views/utils.py | 55 +++++++++++++++++++++------------
6 files changed, 84 insertions(+), 46 deletions(-)
diff --git a/superset/commands/explore/get.py b/superset/commands/explore/get.py
index 78142eb5ec1..caa309f2f39 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,9 +101,12 @@ 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(
- self._datasource_id, self._datasource_type, form_data
+ ds_id, self._datasource_type = get_datasource_info(
+ self._datasource_id,
+ self._datasource_type,
+ form_data,
)
except SupersetException:
self._datasource_id = None
@@ -111,10 +115,11 @@ class GetExploreCommand(BaseCommand, ABC):
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 +129,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..529225cb1a9 100644
--- a/superset/commands/explore/parameters.py
+++ b/superset/commands/explore/parameters.py
@@ -14,14 +14,17 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
+
+from __future__ import annotations
+
from dataclasses import dataclass
from typing import Optional
@dataclass
class CommandParameters:
- permalink_key: Optional[str]
- form_data_key: Optional[str]
- datasource_id: Optional[int]
- datasource_type: Optional[str]
+ permalink_key: str | None
+ form_data_key: str | None
+ datasource_id: int | str | None
+ datasource_type: str | None
slice_id: Optional[int]
diff --git a/superset/daos/datasource.py b/superset/daos/datasource.py
index 308785f625e..0321a082cb9 100644
--- a/superset/daos/datasource.py
+++ b/superset/daos/datasource.py
@@ -16,8 +16,8 @@
# under the License.
import logging
-import uuid
from typing import Union
+from uuid import UUID
from superset import db
from superset.connectors.sqla.models import SqlaTable
@@ -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,
) -> Datasource:
if datasource_type not in cls.sources:
raise DatasourceTypeNotSupportedError()
@@ -57,7 +59,7 @@ class DatasourceDAO(BaseDAO[Datasource]):
filter = model.id == int(database_id_or_uuid)
else:
try:
- uuid.UUID(str(database_id_or_uuid)) # uuid validation
+ UUID(str(database_id_or_uuid)) # uuid validation
filter = model.uuid == database_id_or_uuid
except ValueError as err:
logger.warning(
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 690c00bbefd..97811dee933 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,
@@ -169,9 +170,9 @@ class Superset(BaseSupersetView):
if viz_obj.has_error(payload):
return json_error_response(payload=payload, status=400)
response = {
- "data": payload["df"].to_dict("records")
- if payload["df"] is not None
- else [],
+ "data": (
+ payload["df"].to_dict("records") if payload["df"] is not None
else []
+ ),
"colnames": payload.get("colnames"),
"coltypes": payload.get("coltypes"),
"rowcount": payload.get("rowcount"),
@@ -268,7 +269,9 @@ 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,8 +305,10 @@ class Superset(BaseSupersetView):
form_data = get_form_data()[0]
try:
- datasource_id, datasource_type = get_datasource_info(
- datasource_id, datasource_type, form_data
+ ds_id, datasource_type = get_datasource_info(
+ datasource_id,
+ datasource_type,
+ form_data,
)
force = request.args.get("force") == "true"
@@ -316,7 +321,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 +348,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 +412,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 +456,23 @@ class Superset(BaseSupersetView):
query_context = request.form.get("query_context")
+ ds_id: int | UUID | None = None
try:
- datasource_id, datasource_type = get_datasource_info(
- datasource_id, datasource_type, form_data
+ 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..2c9555d8eae 100644
--- a/superset/views/utils.py
+++ b/superset/views/utils.py
@@ -14,12 +14,16 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
+
+from __future__ import annotations
+
import contextlib
import logging
from collections import defaultdict
from functools import wraps
-from typing import Any, Callable, DefaultDict, Optional, Union
+from typing import Any, Callable, DefaultDict
from urllib import parse
+from uuid import UUID
import msgpack
import pyarrow as pa
@@ -163,7 +167,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:
@@ -186,10 +190,10 @@ def loads_request_json(request_json_data: str) ->
dict[Any, Any]:
def get_form_data(
- slice_id: Optional[int] = None,
+ slice_id: int | None = None,
use_slice_data: bool = False,
- initial_form_data: Optional[dict[str, Any]] = None,
-) -> tuple[dict[str, Any], Optional[Slice]]:
+ initial_form_data: dict[str, Any] | None = None,
+) -> tuple[dict[str, Any], Slice | None]:
form_data: dict[str, Any] = initial_form_data or {}
if has_request_context():
@@ -272,8 +276,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: str | None,
+ form_data: FormData,
+) -> tuple[int | UUID, str | None]:
"""
Compatibility layer for handling of datasource info
@@ -300,12 +306,16 @@ 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(
- sql_results: dict[str, Any], rows: Optional[int] = None
+ sql_results: dict[str, Any], rows: int | None = None
) -> dict[str, Any]:
"""
Given a `sql_results` nested structure, applies a limit to the number of
rows
@@ -482,8 +492,8 @@ 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_type: str | None = None,
+ datasource_id: int | str | None = None,
**kwargs: Any,
) -> None:
"""
@@ -500,8 +510,10 @@ 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(
- datasource_id, datasource_type, form_data
+ ds_id, datasource_type = get_datasource_info(
+ datasource_id,
+ datasource_type,
+ form_data,
)
except SupersetException as ex:
raise SupersetSecurityException(
@@ -524,7 +536,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,
)
@@ -541,7 +553,9 @@ def check_datasource_perms(
def _deserialize_results_payload(
- payload: Union[bytes, str], query: Query, use_msgpack: Optional[bool] =
False
+ payload: bytes | str,
+ query: Query,
+ use_msgpack: bool | None = False,
) -> dict[str, Any]:
logger.debug("Deserializing from msgpack: %r", use_msgpack)
if use_msgpack:
@@ -579,9 +593,12 @@ def _deserialize_results_payload(
def get_cta_schema_name(
- database: Database, user: ab_models.User, schema: str, sql: str
-) -> Optional[str]:
- func: Optional[Callable[[Database, ab_models.User, str, str], str]] =
app.config[
+ database: Database,
+ user: ab_models.User,
+ schema: str,
+ sql: str,
+) -> str | None:
+ func: Callable[[Database, ab_models.User, str, str], str] | None =
app.config[
"SQLLAB_CTAS_SCHEMA_NAME_FUNC"
]
if not func: