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

beto pushed a commit to branch semantic-layer-explore-integration
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 3e0473606a3aeaf7f4bbb84679a79bdd2f0cac0d
Author: Beto Dealmeida <[email protected]>
AuthorDate: Fri Feb 6 16:28:08 2026 -0500

    feat: Explore integration
---
 .../superset-ui-core/src/query/DatasourceKey.ts    | 17 +++++--
 .../superset-ui-core/src/query/types/Datasource.ts |  3 +-
 .../superset-ui-core/src/query/types/Query.ts      |  2 +-
 .../src/components/Chart/DrillBy/DrillByModal.tsx  |  8 +++-
 superset-frontend/src/dashboard/types.ts           |  2 +-
 .../src/explore/actions/saveModalActions.ts        |  5 +-
 .../src/explore/exploreUtils/formData.ts           |  8 ++--
 superset/commands/explore/get.py                   | 19 ++++++--
 superset/commands/explore/parameters.py            | 11 +++--
 superset/daos/datasource.py                        |  8 ++--
 superset/explore/api.py                            |  2 +-
 ...6_33d7e0e21daa_add_semantic_layers_and_views.py |  4 +-
 superset/static/service-worker.js                  | 28 +----------
 superset/views/core.py                             | 35 ++++++++------
 superset/views/utils.py                            | 55 ++++++++++++++--------
 15 files changed, 117 insertions(+), 90 deletions(-)

diff --git 
a/superset-frontend/packages/superset-ui-core/src/query/DatasourceKey.ts 
b/superset-frontend/packages/superset-ui-core/src/query/DatasourceKey.ts
index 38a38e10b13..170f09331b9 100644
--- a/superset-frontend/packages/superset-ui-core/src/query/DatasourceKey.ts
+++ b/superset-frontend/packages/superset-ui-core/src/query/DatasourceKey.ts
@@ -19,16 +19,25 @@
 
 import { DatasourceType } from './types/Datasource';
 
+const DATASOURCE_TYPE_MAP: Record<string, DatasourceType> = {
+  table: DatasourceType.Table,
+  query: DatasourceType.Query,
+  dataset: DatasourceType.Dataset,
+  sl_table: DatasourceType.SlTable,
+  saved_query: DatasourceType.SavedQuery,
+  semantic_view: DatasourceType.SemanticView,
+};
+
 export default class DatasourceKey {
-  readonly id: number;
+  readonly id: number | string;
 
   readonly type: DatasourceType;
 
   constructor(key: string) {
     const [idStr, typeStr] = key.split('__');
-    this.id = parseInt(idStr, 10);
-    this.type = DatasourceType.Table; // default to SqlaTable model
-    this.type = typeStr === 'query' ? DatasourceType.Query : this.type;
+    const isNumeric = /^\d+$/.test(idStr);
+    this.id = isNumeric ? parseInt(idStr, 10) : idStr;
+    this.type = DATASOURCE_TYPE_MAP[typeStr] ?? DatasourceType.Table;
   }
 
   public toString() {
diff --git 
a/superset-frontend/packages/superset-ui-core/src/query/types/Datasource.ts 
b/superset-frontend/packages/superset-ui-core/src/query/types/Datasource.ts
index 47902cf07ae..8fbf63aa4b3 100644
--- a/superset-frontend/packages/superset-ui-core/src/query/types/Datasource.ts
+++ b/superset-frontend/packages/superset-ui-core/src/query/types/Datasource.ts
@@ -26,6 +26,7 @@ export enum DatasourceType {
   Dataset = 'dataset',
   SlTable = 'sl_table',
   SavedQuery = 'saved_query',
+  SemanticView = 'semantic_view',
 }
 
 export interface Currency {
@@ -37,7 +38,7 @@ export interface Currency {
  * Datasource metadata.
  */
 export interface Datasource {
-  id: number;
+  id: number | string;
   name: string;
   type: DatasourceType;
   columns: Column[];
diff --git 
a/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts 
b/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts
index 14d4e2273b2..c1ecc99fae5 100644
--- a/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts
+++ b/superset-frontend/packages/superset-ui-core/src/query/types/Query.ts
@@ -159,7 +159,7 @@ export interface QueryObject
 
 export interface QueryContext {
   datasource: {
-    id: number;
+    id: number | string;
     type: DatasourceType;
   };
   /** Force refresh of all queries */
diff --git a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx 
b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx
index 6245945de45..978277931e2 100644
--- a/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx
+++ b/superset-frontend/src/components/Chart/DrillBy/DrillByModal.tsx
@@ -90,11 +90,15 @@ const ModalFooter = ({ formData, closeModal }: 
ModalFooterProps) => {
     findPermission('can_explore', 'Superset', state.user?.roles),
   );
 
-  const [datasource_id, datasource_type] = formData.datasource.split('__');
+  const [datasourceIdStr, datasource_type] = formData.datasource.split('__');
+  const isNumeric = /^\d+$/.test(datasourceIdStr);
+  const datasource_id = isNumeric
+    ? parseInt(datasourceIdStr, 10)
+    : datasourceIdStr;
   useEffect(() => {
     // short circuit if the user is embedded as explore is not available
     if (isEmbedded()) return;
-    postFormData(Number(datasource_id), datasource_type, formData, 0)
+    postFormData(datasource_id, datasource_type, formData, 0)
       .then(key => {
         setUrl(
           
`/explore/?form_data_key=${key}&dashboard_page_id=${dashboardPageId}`,
diff --git a/superset-frontend/src/dashboard/types.ts 
b/superset-frontend/src/dashboard/types.ts
index b2bde968368..162031c695c 100644
--- a/superset-frontend/src/dashboard/types.ts
+++ b/superset-frontend/src/dashboard/types.ts
@@ -272,7 +272,7 @@ export type Slice = {
   changed_on: number;
   changed_on_humanized: string;
   modified: string;
-  datasource_id: number;
+  datasource_id: number | string;
   datasource_type: DatasourceType;
   datasource_url: string;
   datasource_name: string;
diff --git a/superset-frontend/src/explore/actions/saveModalActions.ts 
b/superset-frontend/src/explore/actions/saveModalActions.ts
index 978c5cb09ba..ed1d13fa93b 100644
--- a/superset-frontend/src/explore/actions/saveModalActions.ts
+++ b/superset-frontend/src/explore/actions/saveModalActions.ts
@@ -144,12 +144,13 @@ export const getSlicePayload = async (
     ...adhocFilters,
     dashboards,
   };
-  let datasourceId = 0;
+  let datasourceId: number | string = 0;
   let datasourceType: DatasourceType = DatasourceType.Table;
 
   if (formData.datasource) {
     const [id, typeString] = formData.datasource.split('__');
-    datasourceId = parseInt(id, 10);
+    const isNumeric = /^\d+$/.test(id);
+    datasourceId = isNumeric ? parseInt(id, 10) : id;
 
     const formattedTypeString =
       typeString.charAt(0).toUpperCase() + typeString.slice(1);
diff --git a/superset-frontend/src/explore/exploreUtils/formData.ts 
b/superset-frontend/src/explore/exploreUtils/formData.ts
index 9a83d8fd8da..994b7f0b4c5 100644
--- a/superset-frontend/src/explore/exploreUtils/formData.ts
+++ b/superset-frontend/src/explore/exploreUtils/formData.ts
@@ -20,7 +20,7 @@ import { SupersetClient, JsonObject, JsonResponse } from 
'@superset-ui/core';
 import { sanitizeFormData } from 'src/utils/sanitizeFormData';
 
 type Payload = {
-  datasource_id: number;
+  datasource_id: number | string;
   datasource_type: string;
   form_data: string;
   chart_id?: number;
@@ -36,7 +36,7 @@ const assembleEndpoint = (key?: string, tabId?: string) => {
 };
 
 const assemblePayload = (
-  datasourceId: number,
+  datasourceId: number | string,
   datasourceType: string,
   formData: JsonObject,
   chartId?: number,
@@ -53,7 +53,7 @@ const assemblePayload = (
 };
 
 export const postFormData = (
-  datasourceId: number,
+  datasourceId: number | string,
   datasourceType: string,
   formData: JsonObject,
   chartId?: number,
@@ -70,7 +70,7 @@ export const postFormData = (
   }).then((r: JsonResponse) => r.json.key);
 
 export const putFormData = (
-  datasourceId: number,
+  datasourceId: number | string,
   datasourceType: string,
   key: string,
   formData: JsonObject,
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/migrations/versions/2025-11-04_11-26_33d7e0e21daa_add_semantic_layers_and_views.py
 
b/superset/migrations/versions/2025-11-04_11-26_33d7e0e21daa_add_semantic_layers_and_views.py
index 1e3b42c5dc3..cd022dfdd62 100644
--- 
a/superset/migrations/versions/2025-11-04_11-26_33d7e0e21daa_add_semantic_layers_and_views.py
+++ 
b/superset/migrations/versions/2025-11-04_11-26_33d7e0e21daa_add_semantic_layers_and_views.py
@@ -17,7 +17,7 @@
 """add_semantic_layers_and_views
 
 Revision ID: 33d7e0e21daa
-Revises: f5b5f88d8526
+Revises: 9787190b3d89
 Create Date: 2025-11-04 11:26:00.000000
 
 """
@@ -37,7 +37,7 @@ from superset.migrations.shared.utils import (
 
 # revision identifiers, used by Alembic.
 revision = "33d7e0e21daa"
-down_revision = "f5b5f88d8526"
+down_revision = "9787190b3d89"
 
 
 def upgrade():
diff --git a/superset/static/service-worker.js 
b/superset/static/service-worker.js
index 43cb14a4894..394fa207693 100644
--- a/superset/static/service-worker.js
+++ b/superset/static/service-worker.js
@@ -1,27 +1 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// Minimal service worker for PWA file handling support
-self.addEventListener('install', event => {
-  event.waitUntil(self.skipWaiting());
-});
-
-self.addEventListener('activate', event => {
-  event.waitUntil(self.clients.claim());
-});
+(()=>{"use strict";let e;var 
r,t,n,o,a,i,f,u,l,s,d,p,c,v,h,g,y={55725(){self.addEventListener("install",e=>{e.waitUntil(self.skipWaiting())}),self.addEventListener("activate",e=>{e.waitUntil(self.clients.claim())})}},b={};function
 m(e){var r=b[e];if(void 0!==r)return r.exports;var 
t=b[e]={id:e,loaded:!1,exports:{}};return 
y[e].call(t.exports,t,t.exports,m),t.loaded=!0,t.exports}m.m=y,m.c=b,r=[],m.O=(e,t,n,o)=>{if(t){o=o||0;for(var
 a=r.length;a>0&&r[a-1][2]>o;a--)r[a]=r[a-1];r[a]=[t,n,o]; [...]
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:

Reply via email to