chore: abstract models and daos into superset-core (#35259)

This commit is contained in:
Ville Brofeldt
2025-11-14 17:00:44 -08:00
committed by GitHub
parent c955a5dc08
commit c2baba50f9
35 changed files with 948 additions and 431 deletions

View File

@@ -14,3 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""
Apache Superset Core - Public API with core functions of Superset
"""

View File

@@ -14,11 +14,3 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from .types.models import CoreModelsApi
from .types.query import CoreQueryApi
from .types.rest_api import CoreRestApi
models: CoreModelsApi
rest_api: CoreRestApi
query: CoreQueryApi

View File

@@ -0,0 +1,262 @@
# 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.
"""
Data Access Object API for superset-core.
Provides dependency-injected DAO classes that will be replaced by
host implementations during initialization.
Usage:
from superset_core.api.daos import DatasetDAO, DatabaseDAO
# Use standard BaseDAO methods
datasets = DatasetDAO.find_all()
dataset = DatasetDAO.find_one_or_none(id=123)
DatasetDAO.create(attributes={"name": "New Dataset"})
"""
from abc import ABC, abstractmethod
from typing import Any, ClassVar, Generic, TypeVar
from flask_appbuilder.models.filters import BaseFilter
from sqlalchemy.orm import Query as SQLAQuery
from superset_core.api.models import (
Chart,
CoreModel,
Dashboard,
Database,
Dataset,
KeyValue,
Query,
SavedQuery,
Tag,
User,
)
# Type variable bound to our CoreModel
T = TypeVar("T", bound=CoreModel)
class BaseDAO(Generic[T], ABC):
"""
Abstract base class for DAOs.
This ABC defines the base that all DAOs should implement,
providing consistent CRUD operations across Superset and extensions.
"""
# Due to mypy limitations, we can't have `type[T]` here
model_cls: ClassVar[type[Any] | None]
base_filter: ClassVar[BaseFilter | None]
id_column_name: ClassVar[str]
uuid_column_name: ClassVar[str]
@classmethod
@abstractmethod
def find_all(cls) -> list[T]:
"""Get all entities that fit the base_filter."""
...
@classmethod
@abstractmethod
def find_one_or_none(cls, **filter_by: Any) -> T | None:
"""Get the first entity that fits the base_filter."""
...
@classmethod
@abstractmethod
def create(
cls,
item: T | None = None,
attributes: dict[str, Any] | None = None,
) -> T:
"""Create an object from the specified item and/or attributes."""
...
@classmethod
@abstractmethod
def update(
cls,
item: T | None = None,
attributes: dict[str, Any] | None = None,
) -> T:
"""Update an object from the specified item and/or attributes."""
...
@classmethod
@abstractmethod
def delete(cls, items: list[T]) -> None:
"""Delete the specified items."""
...
@classmethod
@abstractmethod
def query(cls, query: SQLAQuery) -> list[T]:
"""Execute query with base_filter applied."""
...
@classmethod
@abstractmethod
def filter_by(cls, **filter_by: Any) -> list[T]:
"""Get all entries that fit the base_filter."""
...
class DatasetDAO(BaseDAO[Dataset]):
"""
Abstract Dataset DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
uuid_column_name = "uuid"
class DatabaseDAO(BaseDAO[Database]):
"""
Abstract Database DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
uuid_column_name = "uuid"
class ChartDAO(BaseDAO[Chart]):
"""
Abstract Chart DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
uuid_column_name = "uuid"
class DashboardDAO(BaseDAO[Dashboard]):
"""
Abstract Dashboard DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
uuid_column_name = "uuid"
class UserDAO(BaseDAO[User]):
"""
Abstract User DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
class QueryDAO(BaseDAO[Query]):
"""
Abstract Query DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
class SavedQueryDAO(BaseDAO[SavedQuery]):
"""
Abstract SavedQuery DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
class TagDAO(BaseDAO[Tag]):
"""
Abstract Tag DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
class KeyValueDAO(BaseDAO[KeyValue]):
"""
Abstract KeyValue DAO interface.
Host implementations will replace this class during initialization
with a concrete implementation providing actual functionality.
"""
# Class variables that will be set by host implementation
model_cls = None
base_filter = None
id_column_name = "id"
__all__ = [
"BaseDAO",
"DatasetDAO",
"DatabaseDAO",
"ChartDAO",
"DashboardDAO",
"UserDAO",
"QueryDAO",
"SavedQueryDAO",
"TagDAO",
"KeyValueDAO",
]

View File

@@ -0,0 +1,295 @@
# 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.
"""
Model API for superset-core.
Provides model classes that will be replaced by host implementations
during initialization for extension developers to use.
Usage:
from superset_core.api.models import Dataset, Database, get_session
# Use as regular model classes
dataset = Dataset(name="My Dataset")
db = Database(database_name="My DB")
session = get_session()
"""
from datetime import datetime
from typing import Any
from uuid import UUID
from flask_appbuilder import Model
from sqlalchemy.orm import scoped_session
class CoreModel(Model):
"""
Abstract base class that extends Flask-AppBuilder's Model.
This base class provides the interface contract for all Superset models.
The host package provides concrete implementations.
"""
__abstract__ = True
class Database(CoreModel):
"""
Abstract class for Database models.
This abstract class defines the contract that database models should implement,
providing consistent database connectivity and metadata operations.
"""
__abstract__ = True
id: int
verbose_name: str
database_name: str | None
@property
def name(self) -> str:
raise NotImplementedError
@property
def backend(self) -> str:
raise NotImplementedError
@property
def data(self) -> dict[str, Any]:
raise NotImplementedError
class Dataset(CoreModel):
"""
Abstract class for Dataset models.
This abstract class defines the contract that dataset models should implement,
providing consistent data source operations and metadata.
It provides the public API for Datasets implemented by the host application.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
uuid: UUID | None
table_name: str | None
main_dttm_col: str | None
database_id: int | None
schema: str | None
catalog: str | None
sql: str | None # For virtual datasets
description: str | None
default_endpoint: str | None
is_featured: bool
filter_select_enabled: bool
offset: int
cache_timeout: int
params: str
perm: str | None
schema_perm: str | None
catalog_perm: str | None
is_managed_externally: bool
external_url: str | None
fetch_values_predicate: str | None
is_sqllab_view: bool
template_params: str | None
extra: str | None # JSON string
normalize_columns: bool
always_filter_main_dttm: bool
folders: str | None # JSON string
class Chart(CoreModel):
"""
Abstract Chart/Slice model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
uuid: UUID | None
slice_name: str | None
datasource_id: int | None
datasource_type: str | None
datasource_name: str | None
viz_type: str | None
params: str | None
query_context: str | None
description: str | None
cache_timeout: int
certified_by: str | None
certification_details: str | None
is_managed_externally: bool
external_url: str | None
class Dashboard(CoreModel):
"""
Abstract Dashboard model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
uuid: UUID | None
dashboard_title: str | None
position_json: str | None
description: str | None
css: str | None
json_metadata: str | None
slug: str | None
published: bool
certified_by: str | None
certification_details: str | None
is_managed_externally: bool
external_url: str | None
class User(CoreModel):
"""
Abstract User model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
username: str | None
email: str | None
first_name: str | None
last_name: str | None
active: bool
class Query(CoreModel):
"""
Abstract Query model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
client_id: str | None
database_id: int | None
sql: str | None
status: str | None
user_id: int | None
progress: int
error_message: str | None
class SavedQuery(CoreModel):
"""
Abstract SavedQuery model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
uuid: UUID | None
label: str | None
sql: str | None
database_id: int | None
description: str | None
user_id: int | None
class Tag(CoreModel):
"""
Abstract Tag model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
# Type hints for expected attributes (no actual field definitions)
id: int
name: str | None
type: str | None
class KeyValue(CoreModel):
"""
Abstract KeyValue model interface.
Host implementations will replace this class during initialization
with concrete implementation providing actual functionality.
"""
__abstract__ = True
id: int
uuid: UUID | None
resource: str | None
value: str | None # Encoded value
expires_on: datetime | None
created_by_fk: int | None
changed_by_fk: int | None
def get_session() -> scoped_session:
"""
Retrieve the SQLAlchemy session to directly interface with the
Superset models.
Host implementations will replace this function during initialization
with a concrete implementation providing actual functionality.
:returns: The SQLAlchemy scoped session instance.
"""
raise NotImplementedError("Function will be replaced during initialization")
__all__ = [
"Dataset",
"Database",
"Chart",
"Dashboard",
"User",
"Query",
"SavedQuery",
"Tag",
"KeyValue",
"CoreModel",
"get_session",
]

View File

@@ -0,0 +1,51 @@
# 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.
"""
Query API for superset-core.
Provides dependency-injected query utility functions that will be replaced by
host implementations during initialization.
Usage:
from superset_core.api.query import get_sqlglot_dialect
dialect = get_sqlglot_dialect(database)
"""
from typing import TYPE_CHECKING
from sqlglot import Dialects
if TYPE_CHECKING:
from superset_core.api.models import Database
def get_sqlglot_dialect(database: "Database") -> Dialects:
"""
Get the SQLGlot dialect for the specified database.
Host implementations will replace this function during initialization
with a concrete implementation providing actual functionality.
:param database: The database instance to get the dialect for.
:returns: The SQLGlot dialect enum corresponding to the database.
"""
raise NotImplementedError("Function will be replaced during initialization")
__all__ = ["get_sqlglot_dialect"]

View File

@@ -0,0 +1,72 @@
# 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.
"""
REST API functions for superset-core.
Provides dependency-injected REST API utility functions that will be replaced by
host implementations during initialization.
Usage:
from superset_core.api.rest_api import add_api, add_extension_api
add_api(MyCustomAPI)
add_extension_api(MyExtensionAPI)
"""
from flask_appbuilder.api import BaseApi
class RestApi(BaseApi):
"""
Base REST API class for Superset with browser login support.
This class extends Flask-AppBuilder's BaseApi and enables browser-based
authentication by default.
"""
allow_browser_login = True
def add_api(api: type[RestApi]) -> None:
"""
Add a REST API to the Superset API.
Host implementations will replace this function during initialization
with a concrete implementation providing actual functionality.
:param api: A REST API instance.
:returns: None.
"""
raise NotImplementedError("Function will be replaced during initialization")
def add_extension_api(api: type[RestApi]) -> None:
"""
Add an extension REST API to the Superset API.
Host implementations will replace this function during initialization
with a concrete implementation providing actual functionality.
:param api: An extension REST API instance. These are placed under
the /extensions resource.
:returns: None.
"""
raise NotImplementedError("Function will be replaced during initialization")
__all__ = ["RestApi", "add_api", "add_extension_api"]

View File

@@ -1,16 +0,0 @@
# 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.

View File

@@ -1,90 +0,0 @@
# 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.
from abc import ABC, abstractmethod
from typing import Any, Type
from flask_sqlalchemy import BaseQuery
from sqlalchemy.orm import scoped_session
class CoreModelsApi(ABC):
"""
Abstract interface for accessing Superset data models.
This class defines the contract for retrieving SQLAlchemy sessions
and model instances for datasets and databases within Superset.
"""
@staticmethod
@abstractmethod
def get_session() -> scoped_session:
"""
Retrieve the SQLAlchemy session to directly interface with the
Superset models.
:returns: The SQLAlchemy scoped session instance.
"""
...
@staticmethod
@abstractmethod
def get_dataset_model() -> Type[Any]:
"""
Retrieve the Dataset (SqlaTable) SQLAlchemy model.
:returns: The Dataset SQLAlchemy model class.
"""
...
@staticmethod
@abstractmethod
def get_database_model() -> Type[Any]:
"""
Retrieve the Database SQLAlchemy model.
:returns: The Database SQLAlchemy model class.
"""
...
@staticmethod
@abstractmethod
def get_datasets(query: BaseQuery | None = None, **kwargs: Any) -> list[Any]:
"""
Retrieve Dataset (SqlaTable) entities.
:param query: A query with the Dataset model as the primary entity for complex
queries.
:param kwargs: Optional keyword arguments to filter datasets using SQLAlchemy's
`filter_by()`.
:returns: SqlaTable entities.
"""
...
@staticmethod
@abstractmethod
def get_databases(query: BaseQuery | None = None, **kwargs: Any) -> list[Any]:
"""
Retrieve Database entities.
:param query: A query with the Database model as the primary entity for complex
queries.
:param kwargs: Optional keyword arguments to filter databases using SQLAlchemy's
`filter_by()`.
:returns: Database entities.
"""
...

View File

@@ -1,41 +0,0 @@
# 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.
from abc import ABC, abstractmethod
from typing import Any
from sqlglot import Dialects
class CoreQueryApi(ABC):
"""
Abstract interface for query-related operations.
This class defines the contract for database query operations,
including dialect handling and query processing.
"""
@staticmethod
@abstractmethod
def get_sqlglot_dialect(database: Any) -> Dialects:
"""
Get the SQLGlot dialect for the specified database.
:param database: The database instance to get the dialect for.
:returns: The SQLGlot dialect enum corresponding to the database.
"""
...

View File

@@ -1,64 +0,0 @@
# 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.
from abc import ABC, abstractmethod
from typing import Type
from flask_appbuilder.api import BaseApi
class RestApi(BaseApi):
"""
Base REST API class for Superset with browser login support.
This class extends Flask-AppBuilder's BaseApi and enables browser-based
authentication by default.
"""
allow_browser_login = True
class CoreRestApi(ABC):
"""
Abstract interface for managing REST APIs in Superset.
This class defines the contract for adding and managing REST APIs,
including both core APIs and extension APIs.
"""
@staticmethod
@abstractmethod
def add_api(api: Type[RestApi]) -> None:
"""
Add a REST API to the Superset API.
:param api: A REST API instance.
:returns: None.
"""
...
@staticmethod
@abstractmethod
def add_extension_api(api: Type[RestApi]) -> None:
"""
Add an extension REST API to the Superset API.
:param api: An extension REST API instance. These are placed under
the /extensions resource.
:returns: None.
"""
...