2022-02-20 17:55:12 +01:00
|
|
|
# Copyright 2021 Collate
|
|
|
|
# Licensed 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.
|
|
|
|
|
|
|
|
"""
|
|
|
|
Validate workflow configs and filters
|
|
|
|
"""
|
|
|
|
import uuid
|
|
|
|
from copy import deepcopy
|
2022-07-13 14:43:48 +02:00
|
|
|
from unittest.mock import patch
|
2022-02-20 17:55:12 +01:00
|
|
|
|
2022-02-22 08:09:02 +01:00
|
|
|
import sqlalchemy as sqa
|
2022-07-13 14:43:48 +02:00
|
|
|
from pytest import raises
|
2022-02-22 08:09:02 +01:00
|
|
|
from sqlalchemy.orm import declarative_base
|
|
|
|
|
2022-08-04 07:22:47 -07:00
|
|
|
from metadata.generated.schema.entity.data.table import (
|
|
|
|
Column,
|
|
|
|
DataType,
|
|
|
|
Table,
|
|
|
|
TableProfilerConfig,
|
|
|
|
)
|
2022-04-12 23:40:21 -07:00
|
|
|
from metadata.generated.schema.entity.services.connections.metadata.openMetadataConnection import (
|
|
|
|
OpenMetadataConnection,
|
|
|
|
)
|
2023-05-20 23:22:53 +02:00
|
|
|
from metadata.generated.schema.entity.services.databaseService import (
|
|
|
|
DatabaseService,
|
|
|
|
DatabaseServiceType,
|
|
|
|
)
|
2022-04-21 17:53:29 +02:00
|
|
|
from metadata.generated.schema.metadataIngestion.databaseServiceProfilerPipeline import (
|
|
|
|
DatabaseServiceProfilerPipeline,
|
2022-04-05 14:32:45 +02:00
|
|
|
)
|
2022-02-20 17:55:12 +01:00
|
|
|
from metadata.generated.schema.type.entityReference import EntityReference
|
2023-03-01 08:20:38 +01:00
|
|
|
from metadata.profiler.api.models import ProfilerProcessorConfig
|
|
|
|
from metadata.profiler.api.workflow import ProfilerWorkflow
|
2023-06-22 12:51:56 +05:30
|
|
|
from metadata.profiler.interface.sqlalchemy.profiler_interface import (
|
2023-03-01 08:20:38 +01:00
|
|
|
SQAProfilerInterface,
|
|
|
|
)
|
2023-04-04 17:16:44 +02:00
|
|
|
from metadata.profiler.processor.default import DefaultProfiler
|
2023-07-12 17:02:32 +02:00
|
|
|
from metadata.profiler.source.base.profiler_source import ProfilerSource
|
2022-02-20 17:55:12 +01:00
|
|
|
|
2022-08-17 12:53:16 +02:00
|
|
|
TABLE = Table(
|
|
|
|
id=uuid.uuid4(),
|
|
|
|
name="users",
|
|
|
|
fullyQualifiedName="service.db.users",
|
|
|
|
columns=[
|
|
|
|
Column(name="id", dataType=DataType.INT),
|
|
|
|
Column(name="name", dataType=DataType.STRING),
|
|
|
|
Column(name="fullname", dataType=DataType.STRING),
|
|
|
|
Column(name="nickname", dataType=DataType.STRING),
|
|
|
|
Column(name="age", dataType=DataType.INT),
|
|
|
|
],
|
|
|
|
database=EntityReference(id=uuid.uuid4(), name="db", type="database"),
|
|
|
|
tableProfilerConfig=TableProfilerConfig(
|
2023-03-01 08:20:38 +01:00
|
|
|
profileSample=80.0,
|
|
|
|
), # type: ignore
|
|
|
|
) # type: ignore
|
2022-08-17 12:53:16 +02:00
|
|
|
|
2022-02-20 17:55:12 +01:00
|
|
|
config = {
|
2022-04-05 14:32:45 +02:00
|
|
|
"source": {
|
|
|
|
"type": "sqlite",
|
|
|
|
"serviceName": "my_service",
|
2022-04-06 17:05:00 +02:00
|
|
|
"serviceConnection": {"config": {"type": "SQLite"}},
|
2022-04-21 17:53:29 +02:00
|
|
|
"sourceConfig": {"config": {"type": "Profiler"}},
|
2022-04-05 14:32:45 +02:00
|
|
|
},
|
2022-02-22 08:09:02 +01:00
|
|
|
"processor": {"type": "orm-profiler", "config": {}},
|
2022-02-20 17:55:12 +01:00
|
|
|
"sink": {"type": "metadata-rest", "config": {}},
|
2022-04-05 14:32:45 +02:00
|
|
|
"workflowConfig": {
|
|
|
|
"openMetadataServerConfig": {
|
|
|
|
"hostPort": "http://localhost:8585/api",
|
2022-09-26 16:19:47 +05:30
|
|
|
"authProvider": "openmetadata",
|
|
|
|
"securityConfig": {
|
2023-03-03 18:33:18 +05:30
|
|
|
"jwtToken": (
|
|
|
|
"eyJraWQiOiJHYjM4OWEtOWY3Ni1nZGpzLWE5MmotMDI0MmJrOTQzNTYiLCJ0eXAiOiJKV1"
|
|
|
|
"QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlzQm90IjpmYWxzZSwiaXNzIjoib"
|
|
|
|
"3Blbi1tZXRhZGF0YS5vcmciLCJpYXQiOjE2NjM5Mzg0NjIsImVtYWlsIjoiYWRtaW5Ab3Blb"
|
|
|
|
"m1ldGFkYXRhLm9yZyJ9.tS8um_5DKu7HgzGBzS1VTA5uUjKWOCU0B_j08WXBiEC0mr0zNREk"
|
|
|
|
"qVfwFDD-d24HlNEbrqioLsBuFRiwIWKc1m_ZlVQbG7P36RUxhuv2vbSp80FKyNM-Tj93FDzq"
|
|
|
|
"91jsyNmsQhyNv_fNr3TXfzzSPjHt8Go0FMMP66weoKMgW2PbXlhVKwEuXUHyakLLzewm9UMe"
|
|
|
|
"QaEiRzhiTMU3UkLXcKbYEJJvfNFcLwSl9W8JCO_l0Yj3ud-qt_nQYEZwqW6u5nfdQllN133i"
|
|
|
|
"ikV4fM5QZsMCnm8Rq1mvLR0y9bmJiD7fwM1tmJ791TUWqmKaTnP49U493VanKpUAfzIiOiIbhg"
|
|
|
|
)
|
2022-09-26 16:19:47 +05:30
|
|
|
},
|
2022-04-05 14:32:45 +02:00
|
|
|
}
|
2022-02-20 17:55:12 +01:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-08-17 12:53:16 +02:00
|
|
|
Base = declarative_base()
|
|
|
|
|
|
|
|
|
|
|
|
class User(Base):
|
|
|
|
__tablename__ = "users"
|
|
|
|
id = sqa.Column(sqa.Integer, primary_key=True)
|
|
|
|
name = sqa.Column(sqa.String(256))
|
|
|
|
fullname = sqa.Column(sqa.String(256))
|
|
|
|
nickname = sqa.Column(sqa.String(256))
|
|
|
|
age = sqa.Column(sqa.Integer)
|
2022-02-20 17:55:12 +01:00
|
|
|
|
2022-08-17 12:53:16 +02:00
|
|
|
|
|
|
|
@patch.object(
|
2022-10-11 15:57:25 +02:00
|
|
|
SQAProfilerInterface,
|
2022-08-17 12:53:16 +02:00
|
|
|
"_convert_table_to_orm_object",
|
|
|
|
return_value=User,
|
|
|
|
)
|
2022-07-13 14:43:48 +02:00
|
|
|
@patch.object(
|
|
|
|
ProfilerWorkflow,
|
|
|
|
"_validate_service_name",
|
|
|
|
return_value=True,
|
|
|
|
)
|
2023-03-03 18:33:18 +05:30
|
|
|
def test_init_workflow(mocked_method, mocked_orm): # pylint: disable=unused-argument
|
2022-02-20 17:55:12 +01:00
|
|
|
"""
|
|
|
|
We can initialise the workflow from a config
|
|
|
|
"""
|
2022-07-13 14:43:48 +02:00
|
|
|
workflow = ProfilerWorkflow.create(config)
|
|
|
|
mocked_method.assert_called()
|
|
|
|
|
2022-04-21 17:53:29 +02:00
|
|
|
assert isinstance(workflow.source_config, DatabaseServiceProfilerPipeline)
|
2022-04-12 23:40:21 -07:00
|
|
|
assert isinstance(workflow.metadata_config, OpenMetadataConnection)
|
2022-08-19 10:52:08 +02:00
|
|
|
assert isinstance(workflow.profiler_config, ProfilerProcessorConfig)
|
2022-02-22 08:09:02 +01:00
|
|
|
|
2022-08-19 10:52:08 +02:00
|
|
|
assert workflow.profiler_config.profiler is None
|
|
|
|
assert workflow.profiler_config.tableConfig is None
|
2022-02-20 17:55:12 +01:00
|
|
|
|
|
|
|
|
2022-07-13 14:43:48 +02:00
|
|
|
@patch.object(
|
|
|
|
ProfilerWorkflow,
|
|
|
|
"_validate_service_name",
|
|
|
|
return_value=True,
|
|
|
|
)
|
|
|
|
def test_filter_entities(mocked_method):
|
2022-02-20 17:55:12 +01:00
|
|
|
"""
|
|
|
|
We can properly filter entities depending on the
|
|
|
|
workflow configuration
|
|
|
|
"""
|
2022-07-13 14:43:48 +02:00
|
|
|
workflow = ProfilerWorkflow.create(config)
|
|
|
|
mocked_method.assert_called()
|
2022-02-20 17:55:12 +01:00
|
|
|
|
|
|
|
service_name = "service"
|
2022-04-06 17:05:00 +02:00
|
|
|
schema_reference1 = EntityReference(
|
|
|
|
id=uuid.uuid4(), name="one_schema", type="databaseSchema"
|
|
|
|
)
|
|
|
|
schema_reference2 = EntityReference(
|
|
|
|
id=uuid.uuid4(), name="another_schema", type="databaseSchema"
|
|
|
|
)
|
2022-02-20 17:55:12 +01:00
|
|
|
|
|
|
|
all_tables = [
|
|
|
|
Table(
|
|
|
|
id=uuid.uuid4(),
|
|
|
|
name="table1",
|
2022-04-06 17:05:00 +02:00
|
|
|
databaseSchema=schema_reference1,
|
|
|
|
fullyQualifiedName=f"{service_name}.db.{schema_reference1.name}.table1",
|
2022-02-20 17:55:12 +01:00
|
|
|
columns=[Column(name="id", dataType=DataType.BIGINT)],
|
|
|
|
),
|
|
|
|
Table(
|
|
|
|
id=uuid.uuid4(),
|
|
|
|
name="table2",
|
2022-04-06 17:05:00 +02:00
|
|
|
databaseSchema=schema_reference1,
|
|
|
|
fullyQualifiedName=f"{service_name}.db.{schema_reference1.name}.table2",
|
2022-02-20 17:55:12 +01:00
|
|
|
columns=[Column(name="id", dataType=DataType.BIGINT)],
|
|
|
|
),
|
|
|
|
Table(
|
|
|
|
id=uuid.uuid4(),
|
|
|
|
name="table3",
|
2022-04-06 17:05:00 +02:00
|
|
|
databaseSchema=schema_reference2,
|
|
|
|
fullyQualifiedName=f"{service_name}.db.{schema_reference2.name}.table3",
|
2022-02-20 17:55:12 +01:00
|
|
|
columns=[Column(name="id", dataType=DataType.BIGINT)],
|
|
|
|
),
|
|
|
|
]
|
|
|
|
|
|
|
|
# Simple workflow does not filter
|
|
|
|
assert len(list(workflow.filter_entities(all_tables))) == 3
|
|
|
|
|
|
|
|
# We can exclude based on the schema name
|
2022-04-21 17:53:29 +02:00
|
|
|
exclude_config = deepcopy(config)
|
2022-08-08 10:43:17 +05:30
|
|
|
exclude_config["source"]["sourceConfig"]["config"]["schemaFilterPattern"] = {
|
|
|
|
"excludes": ["another_schema"]
|
2022-04-21 17:53:29 +02:00
|
|
|
}
|
2022-02-20 17:55:12 +01:00
|
|
|
|
2022-04-21 17:53:29 +02:00
|
|
|
exclude_workflow = ProfilerWorkflow.create(exclude_config)
|
2022-07-13 14:43:48 +02:00
|
|
|
mocked_method.assert_called()
|
2022-08-08 10:43:17 +05:30
|
|
|
assert len(list(exclude_workflow.filter_entities(all_tables))) == 2
|
2022-02-20 17:55:12 +01:00
|
|
|
|
2022-04-21 17:53:29 +02:00
|
|
|
exclude_config = deepcopy(config)
|
2022-08-08 10:43:17 +05:30
|
|
|
exclude_config["source"]["sourceConfig"]["config"]["schemaFilterPattern"] = {
|
|
|
|
"excludes": ["another*"]
|
2022-04-21 17:53:29 +02:00
|
|
|
}
|
2022-02-20 17:55:12 +01:00
|
|
|
|
2022-04-21 17:53:29 +02:00
|
|
|
exclude_workflow = ProfilerWorkflow.create(exclude_config)
|
2022-07-13 14:43:48 +02:00
|
|
|
mocked_method.assert_called()
|
2022-04-21 17:53:29 +02:00
|
|
|
assert len(list(exclude_workflow.filter_entities(all_tables))) == 2
|
2022-02-20 17:55:12 +01:00
|
|
|
|
2022-04-21 17:53:29 +02:00
|
|
|
include_config = deepcopy(config)
|
2022-08-08 10:43:17 +05:30
|
|
|
include_config["source"]["sourceConfig"]["config"]["databaseFilterPattern"] = {
|
|
|
|
"includes": ["db*"]
|
2022-04-21 17:53:29 +02:00
|
|
|
}
|
2022-02-20 17:55:12 +01:00
|
|
|
|
2022-04-21 17:53:29 +02:00
|
|
|
include_workflow = ProfilerWorkflow.create(include_config)
|
2022-07-13 14:43:48 +02:00
|
|
|
mocked_method.assert_called()
|
2022-04-21 17:53:29 +02:00
|
|
|
assert len(list(include_workflow.filter_entities(all_tables))) == 3
|
2022-02-20 17:55:12 +01:00
|
|
|
|
|
|
|
|
2022-08-17 12:53:16 +02:00
|
|
|
@patch.object(
|
2022-10-11 15:57:25 +02:00
|
|
|
SQAProfilerInterface,
|
2022-08-17 12:53:16 +02:00
|
|
|
"_convert_table_to_orm_object",
|
|
|
|
return_value=User,
|
|
|
|
)
|
2022-07-13 14:43:48 +02:00
|
|
|
@patch.object(
|
|
|
|
ProfilerWorkflow,
|
|
|
|
"_validate_service_name",
|
|
|
|
return_value=True,
|
|
|
|
)
|
2023-03-03 18:33:18 +05:30
|
|
|
def test_profile_def(mocked_method, mocked_orm): # pylint: disable=unused-argument
|
2022-02-20 17:55:12 +01:00
|
|
|
"""
|
|
|
|
Validate the definitions of the profile in the JSON
|
|
|
|
"""
|
|
|
|
profile_config = deepcopy(config)
|
2022-08-19 10:52:08 +02:00
|
|
|
config_metrics = ["row_count", "min", "COUNT", "null_count"]
|
|
|
|
config_metrics_label = ["rowCount", "min", "valuesCount", "nullCount"]
|
2022-02-22 08:09:02 +01:00
|
|
|
profile_config["processor"]["config"]["profiler"] = {
|
2022-02-20 17:55:12 +01:00
|
|
|
"name": "my_profiler",
|
2022-08-19 10:52:08 +02:00
|
|
|
"metrics": config_metrics,
|
2022-02-20 17:55:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
profile_workflow = ProfilerWorkflow.create(profile_config)
|
2022-07-13 14:43:48 +02:00
|
|
|
mocked_method.assert_called()
|
2023-03-01 08:20:38 +01:00
|
|
|
|
2023-07-12 17:02:32 +02:00
|
|
|
profiler_source = ProfilerSource(
|
2023-05-20 23:22:53 +02:00
|
|
|
profile_workflow.config,
|
|
|
|
DatabaseService(
|
|
|
|
id=uuid.uuid4(),
|
|
|
|
name="myDataBaseService",
|
|
|
|
serviceType=DatabaseServiceType.SQLite,
|
|
|
|
), # type: ignore
|
|
|
|
profile_workflow.metadata,
|
|
|
|
)
|
|
|
|
profiler_runner = profiler_source.get_profiler_runner(
|
|
|
|
TABLE, profile_workflow.profiler_config
|
2022-06-14 21:37:44 +02:00
|
|
|
)
|
2023-03-01 08:20:38 +01:00
|
|
|
|
2023-05-20 23:22:53 +02:00
|
|
|
# profile_workflow.create_profiler(TABLE, profiler_interface)
|
|
|
|
profiler_obj_metrics = [metric.name() for metric in profiler_runner.metrics]
|
2022-02-20 17:55:12 +01:00
|
|
|
|
2022-08-19 10:52:08 +02:00
|
|
|
assert profile_workflow.profiler_config.profiler
|
|
|
|
assert config_metrics_label == profiler_obj_metrics
|
2022-02-22 08:09:02 +01:00
|
|
|
|
|
|
|
|
2022-08-17 12:53:16 +02:00
|
|
|
@patch.object(
|
2022-10-11 15:57:25 +02:00
|
|
|
SQAProfilerInterface,
|
2022-08-17 12:53:16 +02:00
|
|
|
"_convert_table_to_orm_object",
|
|
|
|
return_value=User,
|
|
|
|
)
|
2022-07-13 14:43:48 +02:00
|
|
|
@patch.object(
|
|
|
|
ProfilerWorkflow,
|
|
|
|
"_validate_service_name",
|
|
|
|
return_value=True,
|
|
|
|
)
|
2023-03-03 18:33:18 +05:30
|
|
|
def test_default_profile_def(
|
|
|
|
mocked_method, mocked_orm # pylint: disable=unused-argument
|
|
|
|
):
|
2022-02-22 08:09:02 +01:00
|
|
|
"""
|
|
|
|
If no information is specified for the profiler, let's
|
|
|
|
use the SimpleTableProfiler and SimpleProfiler
|
|
|
|
"""
|
|
|
|
|
|
|
|
profile_workflow = ProfilerWorkflow.create(config)
|
2022-07-13 14:43:48 +02:00
|
|
|
mocked_method.assert_called()
|
2022-02-22 08:09:02 +01:00
|
|
|
|
2023-07-12 17:02:32 +02:00
|
|
|
profiler_source = ProfilerSource(
|
2023-05-20 23:22:53 +02:00
|
|
|
profile_workflow.config,
|
|
|
|
DatabaseService(
|
|
|
|
id=uuid.uuid4(),
|
|
|
|
name="myDataBaseService",
|
|
|
|
serviceType=DatabaseServiceType.SQLite,
|
|
|
|
), # type: ignore
|
|
|
|
profile_workflow.metadata,
|
|
|
|
)
|
|
|
|
profiler_runner = profiler_source.get_profiler_runner(
|
|
|
|
TABLE, profile_workflow.profiler_config
|
2022-03-16 06:05:59 +01:00
|
|
|
)
|
|
|
|
|
2022-02-22 08:09:02 +01:00
|
|
|
assert isinstance(
|
2023-05-20 23:22:53 +02:00
|
|
|
profiler_runner,
|
2022-02-25 18:26:30 +01:00
|
|
|
DefaultProfiler,
|
2022-02-22 08:09:02 +01:00
|
|
|
)
|
2022-02-20 17:55:12 +01:00
|
|
|
|
|
|
|
|
2022-07-13 14:43:48 +02:00
|
|
|
def test_service_name_validation_raised():
|
|
|
|
"""Test the service name validation for the profiler
|
|
|
|
workflow is raised correctly
|
|
|
|
"""
|
|
|
|
with raises(ValueError, match="Service name `.*` does not exist"):
|
|
|
|
ProfilerWorkflow.create(config)
|