mikebridge commented on code in PR #45395: URL: https://github.com/apache/superset/pull/45395#discussion_r4344613589
########## superset-core/tests/semantic_layers/conformance_provider.py: ########## @@ -0,0 +1,237 @@ +# 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. + +"""Test-only SQLite adapter for the conformance examples; no external service.""" + +import sqlite3 +from contextlib import closing +from datetime import date +from typing import Any + +import pyarrow as pa +from example_provider import ExampleView +from superset_core.semantic_layers.errors import ( + SemanticQueryErrorCode, + SemanticQueryRejectedError, + SemanticResultCompletenessError, + SemanticResultCompletenessReason, +) +from superset_core.semantic_layers.types import ( + Dimension, + Filter, + FilterExpression, + Grains, + Metric, + Operator, + OrFilter, + SemanticQuery, + SemanticResult, +) +from superset_core.semantic_layers.view import SemanticViewFeature + + +class Backend: + """Controlled backend responses and a changing metadata catalog.""" + + def __init__(self) -> None: + self.revision: int = 0 + self.fault: bool = False + self.completeness: SemanticResultCompletenessReason | None = None + + def execute(self, sql: str, parameters: list[object]) -> list[tuple[Any, ...]]: + """Execute against deliberately unsorted data, including a NULL group.""" + if self.fault: + raise ConnectionError("test backend unavailable") + connection: sqlite3.Connection + with closing(sqlite3.connect(":memory:")) as connection: + connection.execute( + "CREATE TABLE records(category TEXT, occurred TEXT, amount INT)" + ) + connection.executemany( + "INSERT INTO records VALUES (?, ?, ?)", + [ + ("B", "2026-02-04", 8), Review Comment: Fixed in e088fd1ed70d95d00be72a32abc2a2401cc97120. The seed includes a February C row with amount 6, which satisfies the outer bound but must be excluded by the OR group. The correct result stays NULL=1, A=5, B=8; dropping the group adds C=6. The new mutation regression executes the real SQLite provider and failed before this seed change. The other expected totals have been recalculated independently. ########## superset-core/src/superset_core/semantic_layers/testing.py: ########## @@ -59,3 +79,313 @@ def semantic_layer_registration(monkeypatch: pytest.MonkeyPatch) -> None: integration lane. Use in isolated provider test processes, not a live host. """ monkeypatch.setattr(decorators, "semantic_layer", _registration) + + +# Keep test data and provider construction in the provider's own fixtures. This +# suite consumes the real SDK types rather than a copy of a factory signature. +class SemanticProviderConformance: + """Subclass in a ``Test...`` class and supply the documented provider fixtures. + + Query fixtures seed discriminating data and provide independent expected + Arrow tables for identity, grain, NULL, grouped OR, and order-before-limit. + Error fixtures call real provider methods against controlled backend outcomes. + See the provider author guide for every fixture and the limits of this suite. + """ + + def test_catalog_identity( + self, semantic_view: SemanticView, semantic_reloaded_view: SemanticView + ) -> None: + """Stable selections survive reloads without ambiguous catalog members.""" + assert semantic_view.uid() + assert semantic_view.uid() == semantic_reloaded_view.uid() + assert _catalog(semantic_view) == _catalog(semantic_reloaded_view) + + @pytest.mark.parametrize( + "case", ["identity", "grain", "null", "not_null", "or", "order_limit"] + ) + def test_query_outputs( + self, + case: str, + semantic_view: SemanticView, + semantic_query_cases: Mapping[str, tuple[SemanticQuery, pa.Table]], + ) -> None: + """Compare executed results, including column names and row order.""" + query: SemanticQuery + expected: pa.Table + query, expected = semantic_query_cases[case] + assert expected.num_rows > 0, ( + f"{case} must distinguish a correct result from no data" + ) + assert query.order or expected.num_rows == 1, ( + f"{case}: multi-row fixtures need explicit order" + ) + assert set(query.dimensions) <= semantic_view.get_dimensions() + assert set(query.metrics) <= semantic_view.get_metrics() + if case == "grain": + assert any(dimension.grain is not None for dimension in query.dimensions) Review Comment: Agreed; fixed in e088fd1ed70d95d00be72a32abc2a2401cc97120. The grain case skips with an explicit reason when the view declares no grain-bearing dimension, before looking up the grain fixture entry or executing a query. Such providers can omit that entry without inventing a temporal dimension. Views that declare a grain still run the existing check, and the guide describes the skip as unexercised coverage. Public SDK types and fixture names are unchanged. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
