mirror of
https://github.com/apache/superset.git
synced 2026-07-01 04:15:31 +00:00
Compare commits
1 Commits
master
...
fix-sl-pos
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60a89cce52 |
@@ -43,6 +43,10 @@ from superset_core.semantic_layers.view import (
|
||||
)
|
||||
|
||||
from superset.common.query_object import QueryObject
|
||||
from superset.exceptions import (
|
||||
InvalidPostProcessingError,
|
||||
QueryObjectValidationError,
|
||||
)
|
||||
from superset.explorables.base import TimeGrainDict
|
||||
from superset.extensions import encrypted_field_factory
|
||||
from superset.models.helpers import AuditMixinNullable, QueryResult
|
||||
@@ -281,7 +285,13 @@ class SemanticView(AuditMixinNullable, Model):
|
||||
# =========================================================================
|
||||
|
||||
def get_query_result(self, query_object: QueryObject) -> QueryResult:
|
||||
return get_results(query_object)
|
||||
result = get_results(query_object)
|
||||
if query_object.post_processing and not result.df.empty:
|
||||
try:
|
||||
result.df = query_object.exec_post_processing(result.df)
|
||||
except InvalidPostProcessingError as ex:
|
||||
raise QueryObjectValidationError(ex.message) from ex
|
||||
return result
|
||||
|
||||
def get_query_str(self, query_obj: QueryObjectDict) -> str:
|
||||
return "Not implemented for semantic layers"
|
||||
|
||||
@@ -662,6 +662,7 @@ def test_semantic_view_get_query_result(
|
||||
view = SemanticView()
|
||||
|
||||
mock_query_object = MagicMock()
|
||||
mock_query_object.post_processing = []
|
||||
mock_result = MagicMock()
|
||||
|
||||
with patch(
|
||||
@@ -671,9 +672,115 @@ def test_semantic_view_get_query_result(
|
||||
result = view.get_query_result(mock_query_object)
|
||||
|
||||
mock_get_results.assert_called_once_with(mock_query_object)
|
||||
mock_query_object.exec_post_processing.assert_not_called()
|
||||
assert result == mock_result
|
||||
|
||||
|
||||
def test_semantic_view_get_query_result_runs_post_processing(
|
||||
mock_implementation: MagicMock,
|
||||
) -> None:
|
||||
"""
|
||||
``get_query_result`` must run ``query_object.exec_post_processing`` so that
|
||||
features like ``percent_metrics`` (contribution) are applied to the semantic
|
||||
layer's DataFrame — matching the dataset flow in
|
||||
``superset/models/helpers.py``.
|
||||
"""
|
||||
import pandas as pd
|
||||
|
||||
view = SemanticView()
|
||||
|
||||
input_df = pd.DataFrame({"Orders Count": [40000.0]})
|
||||
processed_df = pd.DataFrame({"Orders Count": [40000.0], "%Orders Count": [1.0]})
|
||||
|
||||
mock_query_object = MagicMock()
|
||||
mock_query_object.post_processing = [
|
||||
{
|
||||
"operation": "contribution",
|
||||
"options": {
|
||||
"columns": ["Orders Count"],
|
||||
"rename_columns": ["%Orders Count"],
|
||||
},
|
||||
}
|
||||
]
|
||||
mock_query_object.exec_post_processing.return_value = processed_df
|
||||
|
||||
mock_result = MagicMock()
|
||||
mock_result.df = input_df
|
||||
|
||||
with patch(
|
||||
"superset.semantic_layers.models.get_results",
|
||||
return_value=mock_result,
|
||||
):
|
||||
result = view.get_query_result(mock_query_object)
|
||||
|
||||
mock_query_object.exec_post_processing.assert_called_once_with(input_df)
|
||||
assert result is mock_result
|
||||
assert list(result.df.columns) == ["Orders Count", "%Orders Count"]
|
||||
|
||||
|
||||
def test_semantic_view_get_query_result_wraps_post_processing_errors(
|
||||
mock_implementation: MagicMock,
|
||||
) -> None:
|
||||
"""
|
||||
``InvalidPostProcessingError`` raised from post-processing must be re-raised
|
||||
as ``QueryObjectValidationError`` so the API surfaces a clean 400 rather
|
||||
than a 500.
|
||||
"""
|
||||
import pandas as pd
|
||||
|
||||
from superset.exceptions import (
|
||||
InvalidPostProcessingError,
|
||||
QueryObjectValidationError,
|
||||
)
|
||||
|
||||
view = SemanticView()
|
||||
|
||||
mock_query_object = MagicMock()
|
||||
mock_query_object.post_processing = [{"operation": "bogus"}]
|
||||
mock_query_object.exec_post_processing.side_effect = InvalidPostProcessingError(
|
||||
"boom"
|
||||
)
|
||||
|
||||
mock_result = MagicMock()
|
||||
mock_result.df = pd.DataFrame({"count": [1]})
|
||||
|
||||
with (
|
||||
patch(
|
||||
"superset.semantic_layers.models.get_results",
|
||||
return_value=mock_result,
|
||||
),
|
||||
pytest.raises(QueryObjectValidationError, match="boom"),
|
||||
):
|
||||
view.get_query_result(mock_query_object)
|
||||
|
||||
|
||||
def test_semantic_view_get_query_result_skips_post_processing_on_empty_df(
|
||||
mock_implementation: MagicMock,
|
||||
) -> None:
|
||||
"""
|
||||
Match the dataset flow's guard: skip post-processing when the DataFrame is
|
||||
empty. Contribution and other ops assume at least one row.
|
||||
"""
|
||||
import pandas as pd
|
||||
|
||||
view = SemanticView()
|
||||
|
||||
mock_query_object = MagicMock()
|
||||
mock_query_object.post_processing = [{"operation": "contribution"}]
|
||||
|
||||
mock_result = MagicMock()
|
||||
mock_result.df = pd.DataFrame()
|
||||
|
||||
with patch(
|
||||
"superset.semantic_layers.models.get_results",
|
||||
return_value=mock_result,
|
||||
):
|
||||
result = view.get_query_result(mock_query_object)
|
||||
|
||||
mock_query_object.exec_post_processing.assert_not_called()
|
||||
assert result is mock_result
|
||||
|
||||
|
||||
def test_semantic_view_data_for_slices(
|
||||
mock_implementation: MagicMock,
|
||||
mock_dimensions: list[Dimension],
|
||||
|
||||
Reference in New Issue
Block a user