Core fixes:
- service_datasource.py: fetch_datasource_metadata() → async
- service.py: create_job(), update_job() → async (callers await)
- _job_routes.py: await create_job/update_job
Maintenance scanners:
- _dashboard_scanner.py: 4 functions → async (find_affected, _get_linked,
_apply_filters, _resolve_title)
- _chart_manager.py: 3 functions → async
- _banner_renderer.py: rebuild_banner → async
- _orchestrators.py: 3 orchestrators → async
- maintenance_banner.py: await async calls
Migration:
- dry_run_orchestrator.py: run(), _build_target_signatures() → async
- risk_assessor.py: build_risks() → async
- migration.py: await service.run()
- mapping_service.py: sync_environment() → async
Dead code:
- _helpers.py: _find_dashboard_id_by_slug marked DEPRECATED
172 lines
6.7 KiB
Python
172 lines
6.7 KiB
Python
# #region DatasourceMetadataService [C:4] [TYPE Module] [SEMANTICS superset, datasource, columns, dialect]
|
|
# @BRIEF Fetch datasource column metadata and database dialect from Superset.
|
|
# @LAYER Domain
|
|
# @RELATION DEPENDS_ON -> [SupersetClient]
|
|
# @RELATION DEPENDS_ON -> [ConfigManager]
|
|
# @PRE Database session and config manager are available.
|
|
# @POST Datasource metadata is fetched with column details and normalized dialect.
|
|
|
|
from typing import Any
|
|
|
|
from ...core.config_manager import ConfigManager
|
|
from ...core.logger import logger
|
|
from ...core.superset_client import SupersetClient
|
|
from ...schemas.translate import DatasourceColumnResponse, DatasourceColumnsResponse
|
|
|
|
# Supported database dialects for translation
|
|
SUPPORTED_DIALECTS = {
|
|
"postgresql", "mysql", "clickhouse", "sqlite", "mssql",
|
|
"oracle", "snowflake", "bigquery", "redshift", "presto",
|
|
"trino", "druid", "hive", "spark", "databricks",
|
|
}
|
|
|
|
|
|
# #region get_dialect_from_database [C:2] [TYPE Function]
|
|
# @BRIEF Extract normalized dialect string from a Superset database record.
|
|
def get_dialect_from_database(database_record: dict[str, Any]) -> str:
|
|
"""Extract and validate dialect from Superset database record."""
|
|
backend = (
|
|
database_record.get("backend")
|
|
or database_record.get("engine")
|
|
or ""
|
|
).lower().strip()
|
|
|
|
if not backend:
|
|
raise ValueError("Could not determine database dialect from connection")
|
|
|
|
dialect_map = {
|
|
"postgresql": "postgresql", "greenplum": "postgresql",
|
|
"mysql": "mysql", "clickhouse": "clickhouse",
|
|
"clickhousedb": "clickhouse", "sqlite": "sqlite",
|
|
"mssql": "mssql", "oracle": "oracle",
|
|
"snowflake": "snowflake", "bigquery": "bigquery",
|
|
"redshift": "redshift", "presto": "presto",
|
|
"trino": "trino", "druid": "druid",
|
|
"hive": "hive", "spark": "spark", "databricks": "databricks",
|
|
}
|
|
|
|
normalized = dialect_map.get(backend, backend)
|
|
if normalized not in SUPPORTED_DIALECTS:
|
|
raise ValueError(
|
|
f"Unsupported database dialect: '{backend}'. "
|
|
f"Supported dialects: {', '.join(sorted(SUPPORTED_DIALECTS))}"
|
|
)
|
|
return normalized
|
|
# #endregion get_dialect_from_database
|
|
|
|
|
|
# #region fetch_datasource_metadata [C:3] [TYPE Function]
|
|
# @BRIEF Fetch datasource columns and database dialect from Superset.
|
|
async def fetch_datasource_metadata(
|
|
dataset_id: int,
|
|
env_id: str,
|
|
config_manager: ConfigManager,
|
|
) -> tuple[list[dict[str, Any]], str]:
|
|
"""Fetch column metadata and database dialect for a datasource from Superset."""
|
|
environments = config_manager.get_environments()
|
|
env_config = next((e for e in environments if e.id == env_id), None)
|
|
if not env_config:
|
|
raise ValueError(f"Superset environment '{env_id}' not found in configuration")
|
|
|
|
client = SupersetClient(env_config)
|
|
dataset_detail = await client.get_dataset_detail(dataset_id)
|
|
|
|
raw_columns = dataset_detail.get("columns", [])
|
|
columns = []
|
|
for col in raw_columns:
|
|
col_name = col.get("name") or col.get("column_name")
|
|
if not col_name:
|
|
continue
|
|
columns.append({
|
|
"name": str(col_name),
|
|
"type": col.get("type"),
|
|
"is_physical": col.get("is_physical", True),
|
|
"is_dttm": col.get("is_dttm", False),
|
|
"description": col.get("description", ""),
|
|
})
|
|
|
|
database_info = dataset_detail.get("database", {})
|
|
if isinstance(database_info, dict):
|
|
dialect = get_dialect_from_database(database_info)
|
|
else:
|
|
try:
|
|
db_id = dataset_detail.get("database_id")
|
|
if db_id:
|
|
db_record = await client.get_database(int(db_id))
|
|
db_result = db_record.get("result", db_record) if isinstance(db_record, dict) else db_record
|
|
dialect = get_dialect_from_database(db_result)
|
|
else:
|
|
raise ValueError("No database information available for this datasource")
|
|
except Exception as e:
|
|
raise ValueError(f"Could not determine database dialect: {e}")
|
|
|
|
return columns, dialect
|
|
# #endregion fetch_datasource_metadata
|
|
|
|
|
|
# #region detect_virtual_columns [C:2] [TYPE Function]
|
|
# @BRIEF Identify virtual (calculated) columns from column metadata.
|
|
def detect_virtual_columns(columns: list[dict[str, Any]]) -> list[str]:
|
|
"""Return names of columns that are virtual (not physical)."""
|
|
return [col["name"] for col in columns if not col.get("is_physical", True)]
|
|
# #endregion detect_virtual_columns
|
|
|
|
|
|
# #region get_datasource_columns [C:3] [TYPE Function]
|
|
# @BRIEF Fetch datasource column metadata from Superset and return structured response.
|
|
async def get_datasource_columns(
|
|
datasource_id: int,
|
|
env_id: str,
|
|
config_manager: ConfigManager,
|
|
) -> DatasourceColumnsResponse:
|
|
"""Fetch and return column metadata for a given Superset datasource."""
|
|
logger.info(f"[get_datasource_columns] Fetching columns for datasource {datasource_id}")
|
|
|
|
environments = config_manager.get_environments()
|
|
env_config = next((e for e in environments if e.id == env_id), None)
|
|
if not env_config:
|
|
raise ValueError(f"Superset environment '{env_id}' not found")
|
|
|
|
client = SupersetClient(env_config)
|
|
dataset_detail = await client.get_dataset_detail(datasource_id)
|
|
|
|
database_info = dataset_detail.get("database", {})
|
|
dialect = None
|
|
if isinstance(database_info, dict):
|
|
try:
|
|
dialect = get_dialect_from_database(database_info)
|
|
except (ValueError, KeyError):
|
|
dialect = None
|
|
if dialect is None:
|
|
database_id = dataset_detail.get("database_id")
|
|
if database_id:
|
|
db_record = await client.get_database(int(database_id))
|
|
db_result = db_record.get("result", db_record) if isinstance(db_record, dict) else db_record
|
|
dialect = get_dialect_from_database(db_result)
|
|
else:
|
|
raise ValueError("Could not determine database dialect for this datasource")
|
|
|
|
raw_columns = dataset_detail.get("columns", [])
|
|
columns = []
|
|
for col in raw_columns:
|
|
col_name = col.get("name") or col.get("column_name")
|
|
if not col_name:
|
|
continue
|
|
columns.append(DatasourceColumnResponse(
|
|
name=str(col_name),
|
|
type=col.get("type"),
|
|
is_physical=col.get("is_physical", True),
|
|
is_dttm=col.get("is_dttm", False),
|
|
description=col.get("description", ""),
|
|
))
|
|
|
|
return DatasourceColumnsResponse(
|
|
datasource_id=datasource_id,
|
|
datasource_name=dataset_detail.get("table_name"),
|
|
schema_name=dataset_detail.get("schema"),
|
|
database_dialect=dialect,
|
|
columns=columns,
|
|
)
|
|
# #endregion get_datasource_columns
|
|
# #endregion DatasourceMetadataService
|