diff --git a/superset-core/src/superset_core/semantic_layers/decorators.py b/superset-core/src/superset_core/semantic_layers/decorators.py index 50c67fc8028..50dd975702c 100644 --- a/superset-core/src/superset_core/semantic_layers/decorators.py +++ b/superset-core/src/superset_core/semantic_layers/decorators.py @@ -41,12 +41,10 @@ Usage: from __future__ import annotations -from typing import Any, Callable, TypeVar - -from superset_core.semantic_layers.layer import SemanticLayer +from typing import Callable, TypeVar # Type variable for decorated semantic layer classes -T = TypeVar("T", bound=type[SemanticLayer[Any, Any]]) +T = TypeVar("T") def semantic_layer( diff --git a/superset/core/api/core_api_injection.py b/superset/core/api/core_api_injection.py index 4d2e88fcf7f..fb39f5d271a 100644 --- a/superset/core/api/core_api_injection.py +++ b/superset/core/api/core_api_injection.py @@ -237,7 +237,7 @@ def inject_semantic_layer_implementations() -> None: """ import superset_core.semantic_layers.decorators as core_sl_module - from superset.extensions.context import get_current_extension_context + import superset.extensions.context as context_module from superset.semantic_layers.registry import registry def semantic_layer_impl( @@ -246,9 +246,7 @@ def inject_semantic_layer_implementations() -> None: description: str | None = None, ) -> Callable[[Any], Any]: def decorator(cls: Any) -> Any: - context = get_current_extension_context() - - if context: + if context := context_module.get_current_extension_context(): manifest = context.manifest prefixed_id = f"extensions.{manifest.publisher}.{manifest.name}.{id}" else: diff --git a/tests/unit_tests/semantic_layers/decorators_test.py b/tests/unit_tests/semantic_layers/decorators_test.py index 2d475c55bbc..e52f6532de2 100644 --- a/tests/unit_tests/semantic_layers/decorators_test.py +++ b/tests/unit_tests/semantic_layers/decorators_test.py @@ -37,26 +37,27 @@ def test_semantic_layer_stub_raises() -> None: def test_inject_semantic_layer_host_context() -> None: """The injected decorator registers a class in host context.""" + from superset.core.api.core_api_injection import ( + inject_semantic_layer_implementations, + ) from superset.semantic_layers.registry import registry # Clear registry for test isolation registry.clear() - with patch( - "superset.core.api.core_api_injection.get_current_extension_context", - return_value=None, - ): - from superset.core.api.core_api_injection import ( - inject_semantic_layer_implementations, - ) - - inject_semantic_layer_implementations() + inject_semantic_layer_implementations() import superset_core.semantic_layers.decorators as mod - @mod.semantic_layer(id="test_layer", name="Test Layer", description="A test") - class FakeLayer: - pass + # Host context: no extension context active, so no prefix + with patch( + "superset.extensions.context.get_current_extension_context", + return_value=None, + ): + + @mod.semantic_layer(id="test_layer", name="Test Layer", description="A test") + class FakeLayer: + pass assert "test_layer" in registry assert registry["test_layer"] is FakeLayer @@ -69,6 +70,9 @@ def test_inject_semantic_layer_host_context() -> None: def test_inject_semantic_layer_extension_context() -> None: """The injected decorator prefixes ID in extension context.""" + from superset.core.api.core_api_injection import ( + inject_semantic_layer_implementations, + ) from superset.semantic_layers.registry import registry registry.clear() @@ -77,29 +81,19 @@ def test_inject_semantic_layer_extension_context() -> None: mock_context.manifest.publisher = "acme" mock_context.manifest.name = "analytics" - with patch( - "superset.core.api.core_api_injection.get_current_extension_context", - return_value=None, - ): - from superset.core.api.core_api_injection import ( - inject_semantic_layer_implementations, - ) - - inject_semantic_layer_implementations() + inject_semantic_layer_implementations() import superset_core.semantic_layers.decorators as mod - # Now simulate extension context for the decorator call + # Extension context is checked at decorator call time via module lookup with patch( - "superset.core.api.core_api_injection.get_current_extension_context", + "superset.extensions.context.get_current_extension_context", return_value=mock_context, ): - # Re-inject so the closure captures the mock context - inject_semantic_layer_implementations() - @mod.semantic_layer(id="ext_layer", name="Extension Layer") - class ExtLayer: - pass + @mod.semantic_layer(id="ext_layer", name="Extension Layer") + class ExtLayer: + pass expected_id = "extensions.acme.analytics.ext_layer" assert expected_id in registry diff --git a/tests/unit_tests/semantic_layers/mapper_test.py b/tests/unit_tests/semantic_layers/mapper_test.py index 59587722065..7abdce91a6e 100644 --- a/tests/unit_tests/semantic_layers/mapper_test.py +++ b/tests/unit_tests/semantic_layers/mapper_test.py @@ -1004,7 +1004,7 @@ def test_convert_query_object_filter_unknown_operator( mock_datasource: MagicMock, ) -> None: """ - Test filter with unknown operator returns None. + Test filter with unknown operator raises ValueError. """ all_dimensions = { dim.name: dim for dim in mock_datasource.implementation.dimensions @@ -1016,9 +1016,8 @@ def test_convert_query_object_filter_unknown_operator( "val": "Electronics", } - result = _convert_query_object_filter(filter_, all_dimensions) - - assert result is None + with pytest.raises(ValueError, match="Unsupported filter operator"): + _convert_query_object_filter(filter_, all_dimensions) def test_validate_query_object_undefined_metric_error(