This is an automated email from the ASF dual-hosted git repository.
beto 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 bb3871ddaf feat: per-db add metrics (#20990)
bb3871ddaf is described below
commit bb3871ddaf8835c67d9cb498a2ca99a9a48a1ec3
Author: Beto Dealmeida <[email protected]>
AuthorDate: Mon Aug 8 12:07:16 2022 -0700
feat: per-db add metrics (#20990)
* feat: per-db add metrics
* Add unit tests
---
superset/connectors/sqla/models.py | 13 ++-----
superset/db_engine_specs/base.py | 35 ++++++++++++++++++
superset/models/core.py | 9 ++++-
tests/unit_tests/models/__init__.py | 16 ++++++++
tests/unit_tests/models/core_test.py | 72 ++++++++++++++++++++++++++++++++++++
5 files changed, 135 insertions(+), 10 deletions(-)
diff --git a/superset/connectors/sqla/models.py
b/superset/connectors/sqla/models.py
index cb1673c828..7ac5675e6e 100644
--- a/superset/connectors/sqla/models.py
+++ b/superset/connectors/sqla/models.py
@@ -1932,7 +1932,10 @@ class SqlaTable(Model, BaseDatasource): # pylint:
disable=too-many-public-metho
:return: Tuple with lists of added, removed and modified column names.
"""
new_columns = self.external_metadata()
- metrics = []
+ metrics = [
+ SqlMetric(**metric)
+ for metric in self.database.get_metrics(self.table_name,
self.schema)
+ ]
any_date_col = None
db_engine_spec = self.db_engine_spec
@@ -1989,14 +1992,6 @@ class SqlaTable(Model, BaseDatasource): # pylint:
disable=too-many-public-metho
columns.extend([col for col in old_columns if col.expression])
self.columns = columns
- metrics.append(
- SqlMetric(
- metric_name="count",
- verbose_name="COUNT(*)",
- metric_type="count",
- expression="COUNT(*)",
- )
- )
if not self.main_dttm_col:
self.main_dttm_col = any_date_col
self.add_missing_metrics(metrics)
diff --git a/superset/db_engine_specs/base.py b/superset/db_engine_specs/base.py
index e95e39c1fb..368770e261 100644
--- a/superset/db_engine_specs/base.py
+++ b/superset/db_engine_specs/base.py
@@ -154,6 +154,21 @@ class LimitMethod: # pylint:
disable=too-few-public-methods
FORCE_LIMIT = "force_limit"
+class MetricType(TypedDict, total=False):
+ """
+ Type for metrics return by `get_metrics`.
+ """
+
+ metric_name: str
+ expression: str
+ verbose_name: Optional[str]
+ metric_type: Optional[str]
+ description: Optional[str]
+ d3format: Optional[str]
+ warning_text: Optional[str]
+ extra: Optional[str]
+
+
class BaseEngineSpec: # pylint: disable=too-many-public-methods
"""Abstract class for database engine specific configurations
@@ -1054,6 +1069,26 @@ class BaseEngineSpec: # pylint:
disable=too-many-public-methods
"""
return inspector.get_columns(table_name, schema)
+ @classmethod
+ def get_metrics( # pylint: disable=unused-argument
+ cls,
+ database: "Database",
+ inspector: Inspector,
+ table_name: str,
+ schema: Optional[str],
+ ) -> List[MetricType]:
+ """
+ Get all metrics from a given schema and table.
+ """
+ return [
+ {
+ "metric_name": "count",
+ "verbose_name": "COUNT(*)",
+ "metric_type": "count",
+ "expression": "COUNT(*)",
+ }
+ ]
+
@classmethod
def where_latest_partition( # pylint:
disable=too-many-arguments,unused-argument
cls,
diff --git a/superset/models/core.py b/superset/models/core.py
index 617c23ef9e..b5a4aa6537 100755
--- a/superset/models/core.py
+++ b/superset/models/core.py
@@ -55,7 +55,7 @@ from sqlalchemy.sql import expression, Select
from superset import app, db_engine_specs, is_feature_enabled
from superset.databases.utils import make_url_safe
-from superset.db_engine_specs.base import TimeGrain
+from superset.db_engine_specs.base import MetricType, TimeGrain
from superset.extensions import cache_manager, encrypted_field_factory,
security_manager
from superset.models.helpers import AuditMixinNullable, ImportExportMixin
from superset.models.tags import FavStarUpdater
@@ -693,6 +693,13 @@ class Database(
) -> List[Dict[str, Any]]:
return self.db_engine_spec.get_columns(self.inspector, table_name,
schema)
+ def get_metrics(
+ self,
+ table_name: str,
+ schema: Optional[str] = None,
+ ) -> List[MetricType]:
+ return self.db_engine_spec.get_metrics(self, self.inspector,
table_name, schema)
+
def get_indexes(
self, table_name: str, schema: Optional[str] = None
) -> List[Dict[str, Any]]:
diff --git a/tests/unit_tests/models/__init__.py
b/tests/unit_tests/models/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/tests/unit_tests/models/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/tests/unit_tests/models/core_test.py
b/tests/unit_tests/models/core_test.py
new file mode 100644
index 0000000000..3338ddcb61
--- /dev/null
+++ b/tests/unit_tests/models/core_test.py
@@ -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.
+
+# pylint: disable=import-outside-toplevel
+
+from typing import List, Optional
+
+from pytest_mock import MockFixture
+from sqlalchemy.engine.reflection import Inspector
+
+
+def test_get_metrics(mocker: MockFixture) -> None:
+ """
+ Tests for ``get_metrics``.
+ """
+ from superset.db_engine_specs.base import MetricType
+ from superset.db_engine_specs.sqlite import SqliteEngineSpec
+ from superset.models.core import Database
+
+ database = Database(database_name="my_database",
sqlalchemy_uri="sqlite://")
+ assert database.get_metrics("table") == [
+ {
+ "expression": "COUNT(*)",
+ "metric_name": "count",
+ "metric_type": "count",
+ "verbose_name": "COUNT(*)",
+ }
+ ]
+
+ class CustomSqliteEngineSpec(SqliteEngineSpec):
+ @classmethod
+ def get_metrics(
+ cls,
+ database: Database,
+ inspector: Inspector,
+ table_name: str,
+ schema: Optional[str],
+ ) -> List[MetricType]:
+ return [
+ {
+ "expression": "COUNT(DISTINCT user_id)",
+ "metric_name": "count_distinct_user_id",
+ "metric_type": "count_distinct",
+ "verbose_name": "COUNT(DISTINCT user_id)",
+ },
+ ]
+
+ database.get_db_engine_spec_for_backend = mocker.MagicMock( # type: ignore
+ return_value=CustomSqliteEngineSpec
+ )
+ assert database.get_metrics("table") == [
+ {
+ "expression": "COUNT(DISTINCT user_id)",
+ "metric_name": "count_distinct_user_id",
+ "metric_type": "count_distinct",
+ "verbose_name": "COUNT(DISTINCT user_id)",
+ },
+ ]