You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by en...@apache.org on 2026/10/07 12:51:12 UTC
(superset) branch master updated: fix(semantic): map layer views as a collection (#44902)
This is an automated email from the ASF dual-hosted git repository.
EnxDev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 90b0a1a9160 fix(semantic): map layer views as a collection (#44902)
90b0a1a9160 is described below
commit 90b0a1a9160e742e16bc79fc79c076262242aee2
Author: Mike Bridge <mi...@preset.io>
AuthorDate: Wed Oct 7 06:51:03 2026 -0600
fix(semantic): map layer views as a collection (#44902)
---
superset/semantic_layers/models.py | 4 +-
tests/unit_tests/semantic_layers/models_test.py | 54 ++++++++++++++++++++++++-
2 files changed, 54 insertions(+), 4 deletions(-)
diff --git a/superset/semantic_layers/models.py b/superset/semantic_layers/models.py
index ee97ff22855..d17cef6de62 100644
--- a/superset/semantic_layers/models.py
+++ b/superset/semantic_layers/models.py
@@ -34,7 +34,7 @@ from flask_appbuilder import Model
from flask_babel import lazy_gettext as _
from sqlalchemy import Column, ForeignKey, Integer, String, Text
from sqlalchemy.engine.base import Connection
-from sqlalchemy.orm import relationship
+from sqlalchemy.orm import Mapped, relationship
from sqlalchemy.orm.mapper import Mapper
from sqlalchemy_utils import UUIDType
from sqlalchemy_utils.types.json import JSONType
@@ -193,7 +193,7 @@ class SemanticLayer(AuditMixinNullable, Model):
perm = Column(String(1000), nullable=True)
# Semantic views relationship
- semantic_views: list[SemanticView] = relationship(
+ semantic_views: Mapped[list[SemanticView]] = relationship(
"SemanticView",
back_populates="semantic_layer",
cascade="all, delete-orphan",
diff --git a/tests/unit_tests/semantic_layers/models_test.py b/tests/unit_tests/semantic_layers/models_test.py
index 545077410c2..4306ba4aff0 100644
--- a/tests/unit_tests/semantic_layers/models_test.py
+++ b/tests/unit_tests/semantic_layers/models_test.py
@@ -20,11 +20,13 @@
from __future__ import annotations
import uuid
-from typing import Any
+from typing import Any, cast
from unittest.mock import MagicMock, patch
import pyarrow as pa
import pytest
+from sqlalchemy import inspect, select
+from sqlalchemy.engine import Connection, Engine
from sqlalchemy.orm import Session
from superset_core.semantic_layers.types import (
Dimension,
@@ -1224,6 +1226,55 @@ def test_semantic_view_get_compatible_dimensions(
# =============================================================================
+def test_semantic_layer_loads_all_semantic_views(session: Session) -> None:
+ """A reloaded layer exposes every stored view as a collection."""
+ assert inspect(SemanticLayer).relationships.semantic_views.uselist is True
+ SemanticView.metadata.create_all(session.get_bind())
+ layer: SemanticLayer = SemanticLayer(
+ uuid=uuid.uuid4(), name="Orders", type="test", configuration="{}"
+ )
+ views: list[SemanticView] = [
+ SemanticView(name=name, semantic_layer=layer, configuration="{}")
+ for name in ("Daily", "Monthly")
+ ]
+ session.add_all([layer, *views])
+ session.flush()
+ session.expire(layer, ["semantic_views"])
+
+ assert isinstance(layer.semantic_views, list)
+ assert {view.name for view in layer.semantic_views} == {"Daily", "Monthly"}
+
+
+@pytest.mark.parametrize("load_before_delete", [True, False])
+def test_semantic_layer_delete_removes_multiple_views(
+ session: Session, load_before_delete: bool
+) -> None:
+ """Loaded and unloaded relationships delete all persisted child rows."""
+ engine: Engine = cast(Engine, session.get_bind())
+ connection: Connection
+ with engine.connect() as connection:
+ connection.exec_driver_sql("PRAGMA foreign_keys=ON")
+ SemanticView.metadata.create_all(engine)
+ layer: SemanticLayer = SemanticLayer(
+ uuid=uuid.uuid4(), name="Orders", type="test", configuration="{}"
+ )
+ views: list[SemanticView] = [
+ SemanticView(name=name, semantic_layer=layer, configuration="{}")
+ for name in ("Daily", "Monthly")
+ ]
+ session.add_all([layer, *views])
+ session.flush()
+ view_ids: set[int] = {view.id for view in views}
+ session.expire(layer, ["semantic_views"])
+ if load_before_delete:
+ assert {view.id for view in layer.semantic_views} == view_ids
+
+ session.delete(layer)
+ session.flush()
+
+ assert session.scalars(select(SemanticView.id)).all() == []
+
+
def test_semantic_view_compatible_dimensions_collapse_grains(
mock_implementation: MagicMock,
) -> None:
@@ -2486,7 +2537,6 @@ def test_layer_delete_removes_child_view_permissions(
def test_layer_delete_batches_permission_ownership_queries(session: Any) -> None:
"""The unloaded layer hook batches child permission ownership checks."""
from sqlalchemy import event, inspect
- from sqlalchemy.engine import Connection
from superset import security_manager