- Добавлена автоматическая инициализация Superset (подключение ClickHouse, 6 датасетов, 10 чартов, дашборд) - Переведено хранение метаданных с SQLite на PostgreSQL (shared с Airflow) - Добавлен superset_config.py для конфигурации PostgreSQL - Обновлен Dockerfile.superset: postgresql-client, psycopg2-binary - Обновлен docker-compose.yml: volume mount конфига, SUPERSET_CONFIG_PATH - Исправлены скрипты init_superset.py и create_dashboard.py для работы с shell - Обновлена документация в README.md: раздел Superset с инструкциями Тестирование: - Проверена работа после перезапуска (данные сохраняются) - Проверен чистый запуск с нуля - API и UI доступны
226 lines
8.0 KiB
Python
226 lines
8.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
================================================================================
|
|
Скрипт инициализации Superset для проекта ClickHouse Mini DWH
|
|
================================================================================
|
|
Назначение:
|
|
- Создание подключения к ClickHouse (Database connection)
|
|
- Импорт датасетов из витрин DM-слоя
|
|
|
|
Запуск:
|
|
Внутри контейнера superset:
|
|
python /app/superset_init/init_superset.py
|
|
|
|
Важно:
|
|
Superset использует SQLite по умолчанию (не PostgreSQL).
|
|
|
|
Текущий подход:
|
|
1. CLI для создания подключения к БД
|
|
2. Superset shell для импорта датасетов (требуется app context)
|
|
|
|
Требования:
|
|
- Запущенный ClickHouse с созданными витринами в схеме dm
|
|
- Superset инициализирован (superset db upgrade, admin создан)
|
|
================================================================================
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import logging
|
|
import subprocess
|
|
from typing import Optional
|
|
|
|
# Настройка логирования
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Добавляем путь к superset
|
|
sys.path.insert(0, '/app')
|
|
|
|
|
|
def run_superset_cli(args):
|
|
"""Запуск команды superset CLI"""
|
|
cmd = ['superset'] + args
|
|
logger.info(f"Running: {' '.join(cmd)}")
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
if result.returncode != 0:
|
|
logger.error(f"Command failed: {result.stderr}")
|
|
else:
|
|
logger.info(f"Command output: {result.stdout}")
|
|
return result.returncode == 0, result.stdout, result.stderr
|
|
|
|
|
|
def create_clickhouse_connection():
|
|
"""Создание подключения к ClickHouse через CLI"""
|
|
logger.info("Creating ClickHouse database connection...")
|
|
|
|
# Создаем подключение через set-database-uri
|
|
# clickhouse-connect использует HTTP порт 8123 внутри Docker сети
|
|
success, stdout, stderr = run_superset_cli([
|
|
'set-database-uri',
|
|
'-d', 'clickhouse_dwh',
|
|
'-u', 'clickhouse+connect://default@clickhouse:8123/default'
|
|
])
|
|
|
|
if success:
|
|
logger.info("Successfully created ClickHouse database connection 'clickhouse_dwh'")
|
|
return True
|
|
else:
|
|
logger.error(f"Failed to create database connection: {stderr}")
|
|
logger.info("Please create manually via UI:")
|
|
logger.info("1. Go to: Settings → Database Connections → + Database")
|
|
logger.info("2. Select: ClickHouse")
|
|
logger.info("3. URI: clickhouse+connect://default@clickhouse:8123/default")
|
|
return False
|
|
|
|
|
|
def import_datasets():
|
|
"""Импорт датасетов через Superset shell"""
|
|
logger.info("Importing datasets...")
|
|
|
|
datasets = [
|
|
{
|
|
"table_name": "v_events_enriched",
|
|
"schema": "dm",
|
|
"database_name": "clickhouse_dwh",
|
|
"description": "Полная обогащённая витрина событий (event + click)"
|
|
},
|
|
{
|
|
"table_name": "v_daily_traffic",
|
|
"schema": "dm",
|
|
"database_name": "clickhouse_dwh",
|
|
"description": "Агрегация трафика по дням и измерениям"
|
|
},
|
|
{
|
|
"table_name": "v_utm_effectiveness",
|
|
"schema": "dm",
|
|
"database_name": "clickhouse_dwh",
|
|
"description": "Эффективность UTM-кампаний"
|
|
},
|
|
{
|
|
"table_name": "v_top_pages_daily",
|
|
"schema": "dm",
|
|
"database_name": "clickhouse_dwh",
|
|
"description": "Популярность страниц по дням"
|
|
},
|
|
{
|
|
"table_name": "v_session_overview",
|
|
"schema": "dm",
|
|
"database_name": "clickhouse_dwh",
|
|
"description": "Обзор сессий пользователей"
|
|
},
|
|
{
|
|
"table_name": "dq_summary",
|
|
"schema": "dm",
|
|
"database_name": "clickhouse_dwh",
|
|
"description": "Сводка по качеству данных"
|
|
}
|
|
]
|
|
|
|
# Создаем Python скрипт для выполнения внутри superset shell
|
|
script_lines = [
|
|
"from superset.extensions import db",
|
|
"from superset.models.core import Database",
|
|
"from superset.connectors.sqla.models import SqlaTable",
|
|
"",
|
|
"# Получаем базу данных",
|
|
"database = db.session.query(Database).filter_by(database_name='clickhouse_dwh').first()",
|
|
"if not database:",
|
|
" print('ERROR: Database clickhouse_dwh not found')",
|
|
" exit(1)",
|
|
"",
|
|
"print(f'Found database: {database.database_name} (id={database.id})')",
|
|
"",
|
|
"imported = 0",
|
|
]
|
|
|
|
for ds in datasets:
|
|
script_lines.extend([
|
|
"",
|
|
f"# Dataset: {ds['table_name']}",
|
|
f"existing = db.session.query(SqlaTable).filter_by(table_name='{ds['table_name']}', schema='{ds['schema']}').first()",
|
|
"if existing:",
|
|
f" print(f'Dataset {ds['table_name']} already exists')",
|
|
"else:",
|
|
" try:",
|
|
f" dataset = SqlaTable(table_name='{ds['table_name']}', schema='{ds['schema']}', database_id=database.id, database=database, description='{ds['description']}')",
|
|
" db.session.add(dataset)",
|
|
" db.session.flush()",
|
|
f" print(f'Created dataset: {ds['table_name']}')",
|
|
" imported += 1",
|
|
" except Exception as e:",
|
|
f" print(f'Error creating {ds['table_name']}: {{e}}')",
|
|
" db.session.rollback()",
|
|
])
|
|
|
|
script_lines.extend([
|
|
"",
|
|
"db.session.commit()",
|
|
"print(f'Successfully imported {imported} datasets')",
|
|
])
|
|
|
|
script_content = '\n'.join(script_lines)
|
|
|
|
# Запускаем через superset shell
|
|
cmd = ['superset', 'shell']
|
|
logger.info("Running datasets import via superset shell...")
|
|
|
|
result = subprocess.run(
|
|
cmd,
|
|
input=script_content,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
logger.error(f"Shell command failed: {result.stderr}")
|
|
return False
|
|
|
|
logger.info(f"Shell output:\n{result.stdout}")
|
|
if "ERROR" in result.stdout:
|
|
logger.error("Failed to import some datasets")
|
|
return False
|
|
|
|
logger.info("Datasets imported successfully")
|
|
return True
|
|
|
|
|
|
def main():
|
|
"""Главная функция инициализации"""
|
|
logger.info("=" * 60)
|
|
logger.info("Superset Initialization for ClickHouse Mini DWH")
|
|
logger.info("=" * 60)
|
|
|
|
# Создаем подключение к ClickHouse
|
|
if not create_clickhouse_connection():
|
|
logger.error("Failed to create ClickHouse connection")
|
|
sys.exit(1)
|
|
|
|
# Импортируем датасеты
|
|
try:
|
|
if not import_datasets():
|
|
logger.error("Failed to import datasets")
|
|
logger.info("\nTo create datasets manually:")
|
|
logger.info("1. Go to http://localhost:8088")
|
|
logger.info("2. Datasets → + Dataset")
|
|
logger.info("3. Select 'clickhouse_dwh' database")
|
|
logger.info("4. Select schema 'dm' and desired table")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
logger.error(f"Error importing datasets: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
logger.info("=" * 60)
|
|
logger.info("Superset initialization completed successfully!")
|
|
logger.info("=" * 60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|