chore(lint): apply ruff --fix (4443 auto-fixes)
Auto-fixed categories: - F401: unused imports removed - I001: import blocks sorted - W293: trailing whitespace stripped - UP035: deprecated typing imports replaced - SIM: simplify suggestions applied - ARG: unused args prefixed with underscore - T201: print statements removed - F841: unused variables removed - RUF059: unpacked variables prefixed Remaining ~1500 unfixable errors (C901, B904, N806, E402) require manual work. Backend smoke tests: 13/13 passed.
This commit is contained in:
@@ -7,8 +7,8 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from datetime import date, datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
from datetime import UTC, date, datetime
|
||||
from typing import Any
|
||||
|
||||
from ..core.cot_logger import seed_trace_id
|
||||
from ..models.clean_release import CandidateArtifact, ReleaseCandidate
|
||||
@@ -119,7 +119,7 @@ def run_candidate_register(args: argparse.Namespace) -> int:
|
||||
version=args.version,
|
||||
source_snapshot_ref=args.source_snapshot_ref,
|
||||
created_by=args.created_by,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
created_at=datetime.now(UTC),
|
||||
status=CandidateStatus.DRAFT.value,
|
||||
)
|
||||
repository.save_candidate(candidate)
|
||||
@@ -216,7 +216,7 @@ def run_compliance_run(args: argparse.Namespace) -> int:
|
||||
requested_by=args.actor,
|
||||
manifest_id=args.manifest_id,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except Exception as exc:
|
||||
print(json.dumps({"status": "error", "message": str(exc)}))
|
||||
return 2
|
||||
|
||||
@@ -272,7 +272,7 @@ def run_compliance_status(args: argparse.Namespace) -> int:
|
||||
# @BRIEF Serialize domain models for CLI JSON output across SQLAlchemy/Pydantic variants.
|
||||
# @PRE: value is serializable model or primitive object.
|
||||
# @POST: Returns dictionary payload without mutating value.
|
||||
def _to_payload(value: Any) -> Dict[str, Any]:
|
||||
def _to_payload(value: Any) -> dict[str, Any]:
|
||||
def _normalize(raw: Any) -> Any:
|
||||
if isinstance(raw, datetime):
|
||||
return raw.isoformat()
|
||||
@@ -366,7 +366,7 @@ def run_approve(args: argparse.Namespace) -> int:
|
||||
decided_by=args.actor,
|
||||
comment=args.comment,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except Exception as exc:
|
||||
print(json.dumps({"status": "error", "message": str(exc)}))
|
||||
return 2
|
||||
|
||||
@@ -397,7 +397,7 @@ def run_reject(args: argparse.Namespace) -> int:
|
||||
decided_by=args.actor,
|
||||
comment=args.comment,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except Exception as exc:
|
||||
print(json.dumps({"status": "error", "message": str(exc)}))
|
||||
return 2
|
||||
|
||||
@@ -429,7 +429,7 @@ def run_publish(args: argparse.Namespace) -> int:
|
||||
target_channel=args.target_channel,
|
||||
publication_ref=args.publication_ref,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except Exception as exc:
|
||||
print(json.dumps({"status": "error", "message": str(exc)}))
|
||||
return 2
|
||||
|
||||
@@ -455,7 +455,7 @@ def run_revoke(args: argparse.Namespace) -> int:
|
||||
revoked_by=args.actor,
|
||||
comment=args.comment,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except Exception as exc:
|
||||
print(json.dumps({"status": "error", "message": str(exc)}))
|
||||
return 2
|
||||
|
||||
@@ -468,7 +468,7 @@ def run_revoke(args: argparse.Namespace) -> int:
|
||||
|
||||
# #region main [TYPE Function]
|
||||
# @BRIEF CLI entrypoint for clean release commands.
|
||||
def main(argv: Optional[List[str]] = None) -> int:
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
seed_trace_id()
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
@@ -9,9 +9,9 @@ import curses
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from types import SimpleNamespace
|
||||
from typing import List, Optional, Any, Dict
|
||||
from typing import Any
|
||||
|
||||
# Standardize sys.path for direct execution from project root or scripts dir.
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -28,11 +28,11 @@ from src.models.clean_release import (
|
||||
CleanProfilePolicy,
|
||||
ComplianceViolation,
|
||||
ProfileType,
|
||||
RegistryStatus,
|
||||
ReleaseCandidate,
|
||||
ReleaseCandidateStatus,
|
||||
ResourceSourceEntry,
|
||||
ResourceSourceRegistry,
|
||||
RegistryStatus,
|
||||
ReleaseCandidateStatus,
|
||||
)
|
||||
from src.services.clean_release.approval_service import approve_candidate
|
||||
from src.services.clean_release.artifact_catalog_loader import load_bootstrap_artifacts
|
||||
@@ -124,7 +124,7 @@ class TuiFacadeAdapter:
|
||||
created_by=actor,
|
||||
)
|
||||
|
||||
def get_overview(self, *, candidate_id: str) -> Dict[str, Any]:
|
||||
def get_overview(self, *, candidate_id: str) -> dict[str, Any]:
|
||||
candidate = self.repository.get_candidate(candidate_id)
|
||||
manifests = self.repository.get_manifests_by_candidate(candidate_id)
|
||||
latest_manifest = (
|
||||
@@ -206,11 +206,11 @@ class CleanReleaseTUI:
|
||||
self.facade = TuiFacadeAdapter(self.repo)
|
||||
self.candidate_id = self._resolve_candidate_id()
|
||||
self.status: Any = "READY"
|
||||
self.checks_progress: List[Dict[str, Any]] = []
|
||||
self.violations_list: List[ComplianceViolation] = []
|
||||
self.report_id: Optional[str] = None
|
||||
self.last_error: Optional[str] = None
|
||||
self.overview: Dict[str, Any] = {}
|
||||
self.checks_progress: list[dict[str, Any]] = []
|
||||
self.violations_list: list[ComplianceViolation] = []
|
||||
self.report_id: str | None = None
|
||||
self.last_error: str | None = None
|
||||
self.overview: dict[str, Any] = {}
|
||||
self.refresh_overview()
|
||||
|
||||
curses.start_color()
|
||||
@@ -230,7 +230,7 @@ class CleanReleaseTUI:
|
||||
return repo
|
||||
|
||||
def _bootstrap_demo_repository(self, repository: CleanReleaseRepository) -> None:
|
||||
now = datetime.now(timezone.utc)
|
||||
now = datetime.now(UTC)
|
||||
policy = CleanProfilePolicy(
|
||||
policy_id="POL-ENT-CLEAN",
|
||||
policy_version="1",
|
||||
@@ -240,7 +240,7 @@ class CleanReleaseTUI:
|
||||
prohibited_artifact_categories=["test-data"],
|
||||
effective_from=now,
|
||||
)
|
||||
setattr(policy, "immutable", True)
|
||||
policy.immutable = True
|
||||
repository.save_policy(policy)
|
||||
|
||||
registry = ResourceSourceRegistry(
|
||||
@@ -257,10 +257,10 @@ class CleanReleaseTUI:
|
||||
updated_at=now,
|
||||
updated_by="system",
|
||||
)
|
||||
setattr(registry, "immutable", True)
|
||||
setattr(registry, "allowed_hosts", ["internal-repo.company.com"])
|
||||
setattr(registry, "allowed_schemes", ["https"])
|
||||
setattr(registry, "allowed_source_types", ["artifactory"])
|
||||
registry.immutable = True
|
||||
registry.allowed_hosts = ["internal-repo.company.com"]
|
||||
registry.allowed_schemes = ["https"]
|
||||
registry.allowed_source_types = ["artifactory"]
|
||||
repository.save_registry(registry)
|
||||
candidate = ReleaseCandidate(
|
||||
id="2026.03.03-rc1",
|
||||
@@ -307,10 +307,10 @@ class CleanReleaseTUI:
|
||||
if not bootstrap_path:
|
||||
return
|
||||
|
||||
with open(bootstrap_path, "r", encoding="utf-8") as bootstrap_file:
|
||||
with open(bootstrap_path, encoding="utf-8") as bootstrap_file:
|
||||
payload = json.load(bootstrap_file)
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
now = datetime.now(UTC)
|
||||
candidate = ReleaseCandidate(
|
||||
id=payload.get("candidate_id", "candidate-1"),
|
||||
version=payload.get("version", "1.0.0"),
|
||||
@@ -523,7 +523,7 @@ class CleanReleaseTUI:
|
||||
result = self.facade.run_compliance(
|
||||
candidate_id=self.candidate_id, actor="operator"
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except Exception as exc:
|
||||
self.status = CheckFinalStatus.FAILED
|
||||
self.last_error = str(exc)
|
||||
self.refresh_screen()
|
||||
@@ -563,7 +563,7 @@ class CleanReleaseTUI:
|
||||
self.violations_list = []
|
||||
self.checks_progress = []
|
||||
self.last_error = f"Manifest built: {manifest.id}"
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except Exception as exc:
|
||||
self.last_error = str(exc)
|
||||
self.refresh_overview()
|
||||
self.refresh_screen()
|
||||
@@ -585,7 +585,7 @@ class CleanReleaseTUI:
|
||||
try:
|
||||
self.facade.approve_latest(candidate_id=self.candidate_id, actor="operator")
|
||||
self.last_error = None
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except Exception as exc:
|
||||
self.last_error = str(exc)
|
||||
self.refresh_overview()
|
||||
self.refresh_screen()
|
||||
@@ -598,7 +598,7 @@ class CleanReleaseTUI:
|
||||
try:
|
||||
self.facade.publish_latest(candidate_id=self.candidate_id, actor="operator")
|
||||
self.last_error = None
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except Exception as exc:
|
||||
self.last_error = str(exc)
|
||||
self.refresh_overview()
|
||||
self.refresh_screen()
|
||||
@@ -611,7 +611,7 @@ class CleanReleaseTUI:
|
||||
try:
|
||||
self.facade.publish_latest(candidate_id=self.candidate_id, actor="operator")
|
||||
self.last_error = None
|
||||
except Exception as exc: # noqa: BLE001
|
||||
except Exception as exc:
|
||||
self.last_error = str(exc)
|
||||
self.refresh_overview()
|
||||
self.refresh_screen()
|
||||
|
||||
@@ -8,18 +8,18 @@
|
||||
#
|
||||
# @INVARIANT: Admin user must have the "Admin" role.
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add src to path
|
||||
sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from src.core.database import AuthSessionLocal, init_db
|
||||
from src.core.auth.security import get_password_hash
|
||||
from src.models.auth import User, Role
|
||||
from src.core.logger import logger, belief_scope
|
||||
from src.core.cot_logger import seed_trace_id
|
||||
from src.core.database import AuthSessionLocal, init_db
|
||||
from src.core.logger import belief_scope, logger
|
||||
from src.models.auth import Role, User
|
||||
|
||||
|
||||
# #region create_admin [TYPE Function]
|
||||
|
||||
@@ -14,10 +14,10 @@ from pathlib import Path
|
||||
# Add src to path
|
||||
sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from src.core.cot_logger import seed_trace_id
|
||||
from src.core.database import init_db
|
||||
from src.core.encryption_key import ensure_encryption_key
|
||||
from src.core.logger import logger, belief_scope
|
||||
from src.core.cot_logger import seed_trace_id
|
||||
from src.core.logger import belief_scope, logger
|
||||
from src.scripts.seed_permissions import seed_permissions
|
||||
|
||||
|
||||
|
||||
@@ -14,15 +14,15 @@ import argparse
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
from collections.abc import Iterable
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Iterable, Optional
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from src.core.logger import belief_scope, logger
|
||||
from src.core.cot_logger import seed_trace_id
|
||||
|
||||
from src.core.logger import belief_scope, logger
|
||||
|
||||
# #region Constants [TYPE Section]
|
||||
DEFAULT_TARGET_URL = os.getenv(
|
||||
@@ -62,7 +62,7 @@ def _json_load_if_needed(value: Any) -> Any:
|
||||
|
||||
# #region _find_legacy_config_path [TYPE Function]
|
||||
# @BRIEF Resolves the existing legacy config.json path from candidates.
|
||||
def _find_legacy_config_path(explicit_path: Optional[str]) -> Optional[Path]:
|
||||
def _find_legacy_config_path(explicit_path: str | None) -> Path | None:
|
||||
with belief_scope("_find_legacy_config_path"):
|
||||
if explicit_path:
|
||||
p = Path(explicit_path)
|
||||
@@ -162,7 +162,7 @@ def _ensure_target_schema(engine) -> None:
|
||||
|
||||
# #region _migrate_config [TYPE Function]
|
||||
# @BRIEF Migrates legacy config.json into app_configurations(global).
|
||||
def _migrate_config(engine, legacy_config_path: Optional[Path]) -> int:
|
||||
def _migrate_config(engine, legacy_config_path: Path | None) -> int:
|
||||
with belief_scope("_migrate_config"):
|
||||
if legacy_config_path is None:
|
||||
logger.reason(
|
||||
@@ -196,7 +196,7 @@ def _migrate_config(engine, legacy_config_path: Optional[Path]) -> int:
|
||||
|
||||
# #region _migrate_tasks_and_logs [TYPE Function]
|
||||
# @BRIEF Migrates task_records and task_logs from SQLite into PostgreSQL.
|
||||
def _migrate_tasks_and_logs(engine, sqlite_conn: sqlite3.Connection) -> Dict[str, int]:
|
||||
def _migrate_tasks_and_logs(engine, sqlite_conn: sqlite3.Connection) -> dict[str, int]:
|
||||
with belief_scope("_migrate_tasks_and_logs"):
|
||||
stats = {
|
||||
"task_records_total": 0,
|
||||
@@ -326,8 +326,8 @@ def _migrate_tasks_and_logs(engine, sqlite_conn: sqlite3.Connection) -> Dict[str
|
||||
# #region run_migration [TYPE Function]
|
||||
# @BRIEF Orchestrates migration from SQLite/file to PostgreSQL.
|
||||
def run_migration(
|
||||
sqlite_path: Path, target_url: str, legacy_config_path: Optional[Path]
|
||||
) -> Dict[str, int]:
|
||||
sqlite_path: Path, target_url: str, legacy_config_path: Path | None
|
||||
) -> dict[str, int]:
|
||||
seed_trace_id()
|
||||
with belief_scope("run_migration"):
|
||||
logger.reason(
|
||||
|
||||
@@ -15,11 +15,11 @@ from pathlib import Path
|
||||
# Add src to path
|
||||
sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from src.core.database import AuthSessionLocal
|
||||
from src.models.auth import Permission, Role
|
||||
from src.core.auth.repository import AuthRepository
|
||||
from src.core.logger import logger, belief_scope
|
||||
from src.core.cot_logger import seed_trace_id
|
||||
from src.core.database import AuthSessionLocal
|
||||
from src.core.logger import belief_scope, logger
|
||||
from src.models.auth import Permission, Role
|
||||
|
||||
# #region INITIAL_PERMISSIONS [C:3] [TYPE Constant]
|
||||
# @BRIEF Canonical bootstrap permission tuples seeded into auth storage.
|
||||
|
||||
@@ -12,14 +12,13 @@ import random
|
||||
import sys
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from src.core.config_manager import ConfigManager
|
||||
from src.core.config_models import Environment
|
||||
from src.core.logger import belief_scope, logger
|
||||
from src.core.cot_logger import seed_trace_id
|
||||
from src.core.logger import belief_scope, logger
|
||||
from src.core.superset_client import SupersetClient
|
||||
|
||||
|
||||
@@ -71,7 +70,7 @@ def _parse_args() -> argparse.Namespace:
|
||||
# @BRIEF Normalizes Superset API payloads that may be wrapped in `result`.
|
||||
# @PRE: payload is a JSON-decoded API response.
|
||||
# @POST: Returns the unwrapped object when present.
|
||||
def _extract_result_payload(payload: Dict) -> Dict:
|
||||
def _extract_result_payload(payload: dict) -> dict:
|
||||
result = payload.get("result")
|
||||
if isinstance(result, dict):
|
||||
return result
|
||||
@@ -85,7 +84,7 @@ def _extract_result_payload(payload: Dict) -> Dict:
|
||||
# @BRIEF Extracts object ID from create/update API response.
|
||||
# @PRE: payload is a JSON-decoded API response.
|
||||
# @POST: Returns integer object ID or None if missing.
|
||||
def _extract_created_id(payload: Dict) -> Optional[int]:
|
||||
def _extract_created_id(payload: dict) -> int | None:
|
||||
direct_id = payload.get("id")
|
||||
if isinstance(direct_id, int):
|
||||
return direct_id
|
||||
@@ -142,10 +141,10 @@ def _generate_unique_name(prefix: str, used_names: set[str], rng: random.Random)
|
||||
# @BRIEF Resolves requested environment IDs from configuration.
|
||||
# @PRE: env_ids is non-empty.
|
||||
# @POST: Returns mapping env_id -> configured environment object.
|
||||
def _resolve_target_envs(env_ids: List[str]) -> Dict[str, Environment]:
|
||||
def _resolve_target_envs(env_ids: list[str]) -> dict[str, Environment]:
|
||||
config_manager = ConfigManager()
|
||||
configured = {env.id: env for env in config_manager.get_environments()}
|
||||
resolved: Dict[str, Environment] = {}
|
||||
resolved: dict[str, Environment] = {}
|
||||
|
||||
if not configured:
|
||||
for config_path in [Path("config.json"), Path("backend/config.json")]:
|
||||
@@ -181,7 +180,7 @@ def _resolve_target_envs(env_ids: List[str]) -> Dict[str, Environment]:
|
||||
# @POST: Returns non-empty list of chart payload templates.
|
||||
def _build_chart_template_pool(
|
||||
client: SupersetClient, pool_size: int, rng: random.Random
|
||||
) -> List[Dict]:
|
||||
) -> list[dict]:
|
||||
list_query = {
|
||||
"page": 0,
|
||||
"page_size": 1000,
|
||||
@@ -209,7 +208,7 @@ def _build_chart_template_pool(
|
||||
if len(candidates) <= pool_size
|
||||
else rng.sample(candidates, pool_size)
|
||||
)
|
||||
templates: List[Dict] = []
|
||||
templates: list[dict] = []
|
||||
|
||||
for row in selected:
|
||||
chart_id = int(row["id"])
|
||||
@@ -255,15 +254,15 @@ def _build_chart_template_pool(
|
||||
# @PRE: Target environments must be reachable and authenticated.
|
||||
# @POST: Returns execution statistics dictionary.
|
||||
# @SIDE_EFFECT: Creates objects in Superset environments.
|
||||
def seed_superset_load_data(args: argparse.Namespace) -> Dict:
|
||||
def seed_superset_load_data(args: argparse.Namespace) -> dict:
|
||||
seed_trace_id()
|
||||
rng = random.Random(args.seed)
|
||||
env_map = _resolve_target_envs(args.envs)
|
||||
|
||||
clients: Dict[str, SupersetClient] = {}
|
||||
templates_by_env: Dict[str, List[Dict]] = {}
|
||||
created_dashboards: Dict[str, List[int]] = {env_id: [] for env_id in env_map}
|
||||
created_charts: Dict[str, List[int]] = {env_id: [] for env_id in env_map}
|
||||
clients: dict[str, SupersetClient] = {}
|
||||
templates_by_env: dict[str, list[dict]] = {}
|
||||
created_dashboards: dict[str, list[int]] = {env_id: [] for env_id in env_map}
|
||||
created_charts: dict[str, list[int]] = {env_id: [] for env_id in env_map}
|
||||
used_chart_names: set[str] = set()
|
||||
used_dashboard_names: set[str] = set()
|
||||
|
||||
|
||||
@@ -15,46 +15,46 @@ from pathlib import Path
|
||||
# Add src to path (parent of scripts directory)
|
||||
sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from src.core.superset_client import SupersetClient
|
||||
from src.core.config_manager import ConfigManager
|
||||
from src.core.logger import logger
|
||||
from src.core.cot_logger import seed_trace_id
|
||||
from src.core.logger import logger
|
||||
from src.core.superset_client import SupersetClient
|
||||
|
||||
|
||||
def test_dashboard_dataset_relations():
|
||||
"""Test fetching dataset-to-dashboard relationships."""
|
||||
seed_trace_id()
|
||||
|
||||
|
||||
# Load environment from existing config
|
||||
config_manager = ConfigManager()
|
||||
environments = config_manager.get_environments()
|
||||
|
||||
|
||||
if not environments:
|
||||
logger.error("No environments configured!")
|
||||
return
|
||||
|
||||
|
||||
# Use first available environment
|
||||
env = environments[0]
|
||||
logger.info(f"Using environment: {env.name} ({env.url})")
|
||||
|
||||
|
||||
client = SupersetClient(env)
|
||||
|
||||
|
||||
try:
|
||||
# Authenticate
|
||||
logger.info("Authenticating to Superset...")
|
||||
client.authenticate()
|
||||
logger.info("Authentication successful!")
|
||||
|
||||
|
||||
# Test dashboard ID 13
|
||||
dashboard_id = 13
|
||||
logger.info(f"\n=== Fetching Dashboard {dashboard_id} ===")
|
||||
dashboard = client.network.request(method="GET", endpoint=f"/dashboard/{dashboard_id}")
|
||||
|
||||
|
||||
print("\nDashboard structure:")
|
||||
print(f" ID: {dashboard.get('id')}")
|
||||
print(f" Title: {dashboard.get('dashboard_title')}")
|
||||
print(f" Published: {dashboard.get('published')}")
|
||||
|
||||
|
||||
# Check for slices/charts
|
||||
if 'slices' in dashboard:
|
||||
logger.info(f"\n Found {len(dashboard['slices'])} slices/charts in dashboard")
|
||||
@@ -72,21 +72,21 @@ def test_dashboard_dataset_relations():
|
||||
else:
|
||||
logger.warning(" No 'slices' field found in dashboard response")
|
||||
logger.info(f" Available fields: {list(dashboard.keys())}")
|
||||
|
||||
|
||||
# Test dataset ID 26
|
||||
dataset_id = 26
|
||||
logger.info(f"\n=== Fetching Dataset {dataset_id} ===")
|
||||
dataset = client.get_dataset(dataset_id)
|
||||
|
||||
|
||||
print("\nDataset structure:")
|
||||
print(f" ID: {dataset.get('id')}")
|
||||
print(f" Table Name: {dataset.get('table_name')}")
|
||||
print(f" Schema: {dataset.get('schema')}")
|
||||
print(f" Database: {dataset.get('database', {}).get('database_name', 'Unknown')}")
|
||||
|
||||
|
||||
# Check for dashboards that use this dataset
|
||||
logger.info(f"\n=== Finding Dashboards using Dataset {dataset_id} ===")
|
||||
|
||||
|
||||
# Method: Use Superset's related_objects API
|
||||
try:
|
||||
logger.info(f" Using /api/v1/dataset/{dataset_id}/related_objects endpoint...")
|
||||
@@ -94,15 +94,15 @@ def test_dashboard_dataset_relations():
|
||||
method="GET",
|
||||
endpoint=f"/dataset/{dataset_id}/related_objects"
|
||||
)
|
||||
|
||||
|
||||
logger.info(f" Related objects response type: {type(related_objects)}")
|
||||
logger.info(f" Related objects keys: {list(related_objects.keys()) if isinstance(related_objects, dict) else 'N/A'}")
|
||||
|
||||
|
||||
# Check for dashboards in related objects
|
||||
if 'dashboards' in related_objects:
|
||||
dashboards = related_objects['dashboards']
|
||||
logger.info(f" Found {len(dashboards)} dashboards using this dataset:")
|
||||
|
||||
|
||||
for dash in dashboards:
|
||||
if isinstance(dash, dict):
|
||||
logger.info(f" - Dashboard ID {dash.get('id')}: {dash.get('dashboard_title', dash.get('title', 'Unknown'))}")
|
||||
@@ -114,27 +114,27 @@ def test_dashboard_dataset_relations():
|
||||
if 'dashboards' in result:
|
||||
dashboards = result['dashboards']
|
||||
logger.info(f" Found {len(dashboards)} dashboards using this dataset:")
|
||||
|
||||
|
||||
for dash in dashboards:
|
||||
logger.info(f" - Dashboard ID {dash.get('id')}: {dash.get('dashboard_title', dash.get('title', 'Unknown'))}")
|
||||
else:
|
||||
logger.warning(f" No 'dashboards' key in result. Keys: {list(result.keys())}")
|
||||
else:
|
||||
logger.warning(f" No 'dashboards' key in response. Available keys: {list(related_objects.keys())}")
|
||||
logger.info(f" Full related_objects response:")
|
||||
logger.info(" Full related_objects response:")
|
||||
print(json.dumps(related_objects, indent=2, default=str)[:1000])
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f" Error fetching related objects: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
# Method 2: Try to use the position_json from dashboard
|
||||
logger.info(f"\n=== Analyzing Dashboard Position JSON ===")
|
||||
logger.info("\n=== Analyzing Dashboard Position JSON ===")
|
||||
if 'position_json' in dashboard:
|
||||
position_data = json.loads(dashboard['position_json'])
|
||||
logger.info(f" Position data type: {type(position_data)}")
|
||||
|
||||
|
||||
# Look for datasource references
|
||||
datasource_ids = set()
|
||||
if isinstance(position_data, dict):
|
||||
@@ -148,10 +148,10 @@ def test_dashboard_dataset_relations():
|
||||
if isinstance(item, dict):
|
||||
if 'datasource_id' in item:
|
||||
datasource_ids.add(item['datasource_id'])
|
||||
|
||||
|
||||
if datasource_ids:
|
||||
logger.info(f" Found datasource IDs: {datasource_ids}")
|
||||
|
||||
|
||||
# Save full response for analysis
|
||||
output_file = Path(__file__).parent / "dataset_dashboard_analysis.json"
|
||||
with open(output_file, 'w') as f:
|
||||
@@ -160,7 +160,7 @@ def test_dashboard_dataset_relations():
|
||||
'dataset': dataset
|
||||
}, f, indent=2, default=str)
|
||||
logger.info(f"\nFull response saved to: {output_file}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user