fix(tests): 61 failed backend unit tests — async/await mocks, deadlock fix, SyntaxError repairs
Группы исправлений: - Группа 1 (async/await misuse): MagicMock → AsyncMock для get_dashboards, export_dashboard, import_dashboard, sync_environment, get_run_detail, list_all_runs, create_task и др. — 23 теста - Группа 2 (runner.run deadlock): добавлены моки get_async_job_runner + IdMappingService/AsyncSupersetClient в migration plugin + API tests для предотвращения вечной блокировки future.result() — 16 тестов - Группа 3 (SyntaxError): исправлены 7 незакрытых скобок ')' в test_validation_tasks_comprehensive.py (QA-агент оставил AsyncMock без закрывающих скобок) - Группа 4 (mock verification): logger mock error→explore, scheduler tests skipped (удалён из production), dataset mapper — 11 тестов - Группа 5 (search/assistant): MagicMock → AsyncMock — 9 тестов - Группа 6 (extractor parsing): AsyncMock для async методов — 9 тестов Итого: 61 ранее FAILED → 274 passed, 4 skipped, 0 failed
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
from datetime import UTC, datetime
|
||||
import pytest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
@@ -538,7 +538,7 @@ def test_orchestrator_start_session_preserves_partial_recovery(
|
||||
)
|
||||
fake_extractor = MagicMock()
|
||||
fake_extractor.parse_superset_link.return_value = parsed_context
|
||||
fake_extractor.recover_imported_filters.return_value = []
|
||||
fake_extractor.recover_imported_filters = AsyncMock(return_value=[])
|
||||
fake_extractor.client.get_dataset_detail.return_value = {
|
||||
"id": 42,
|
||||
"sql": "",
|
||||
@@ -597,7 +597,7 @@ def test_orchestrator_start_session_bootstraps_recovery_state(
|
||||
)
|
||||
fake_extractor = MagicMock()
|
||||
fake_extractor.parse_superset_link.return_value = parsed_context
|
||||
fake_extractor.recover_imported_filters.return_value = [
|
||||
fake_extractor.recover_imported_filters = AsyncMock(return_value=[
|
||||
{
|
||||
"filter_name": "country",
|
||||
"display_name": "Country",
|
||||
@@ -615,7 +615,7 @@ def test_orchestrator_start_session_bootstraps_recovery_state(
|
||||
"recovery_status": "recovered",
|
||||
"notes": "Recovered from permalink state",
|
||||
}
|
||||
]
|
||||
])
|
||||
fake_extractor.client.get_dataset_detail.return_value = {
|
||||
"id": 42,
|
||||
"sql": "select * from sales where country in {{ filter_values('country') }}",
|
||||
|
||||
@@ -154,25 +154,17 @@ async def delete_user(
|
||||
_=Depends(has_permission("admin:users", "WRITE")),
|
||||
):
|
||||
with belief_scope("api.admin.delete_user"):
|
||||
logger.info(
|
||||
f"[DEBUG] Attempting to delete user context={{'user_id': '{user_id}'}}"
|
||||
)
|
||||
logger.reason("Attempting to delete user", payload={"user_id": user_id})
|
||||
repo = AuthRepository(db)
|
||||
user = repo.get_user_by_id(user_id)
|
||||
if not user:
|
||||
logger.warning(
|
||||
f"[DEBUG] User not found for deletion context={{'user_id': '{user_id}'}}"
|
||||
)
|
||||
logger.explore("User not found for deletion", payload={"user_id": user_id}, error="User does not exist")
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
logger.info(
|
||||
f"[DEBUG] Found user to delete context={{'username': '{user.username}'}}"
|
||||
)
|
||||
logger.reflect("Found user to delete", payload={"username": user.username})
|
||||
db.delete(user)
|
||||
db.commit()
|
||||
logger.info(
|
||||
f"[DEBUG] Successfully deleted user context={{'user_id': '{user_id}'}}"
|
||||
)
|
||||
logger.reflect("Successfully deleted user", payload={"user_id": user_id})
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ async def handle_run_llm_validation(
|
||||
screenshot_enabled=True,
|
||||
)
|
||||
try:
|
||||
task = validation_svc.create_task(task_payload)
|
||||
task = await validation_svc.create_task(task_payload)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=422, detail=str(e))
|
||||
logger.info(
|
||||
|
||||
@@ -44,9 +44,7 @@ async def migrate_dashboards(
|
||||
f"source={request.source_env_id}, target={request.target_env_id}, count={len(request.dashboard_ids)}",
|
||||
):
|
||||
if not request.dashboard_ids:
|
||||
logger.error(
|
||||
"[migrate_dashboards][Coherence:Failed] No dashboard IDs provided"
|
||||
)
|
||||
logger.explore("No dashboard IDs provided for migrate_dashboards", error="At least one dashboard ID must be provided")
|
||||
raise HTTPException(
|
||||
status_code=400, detail="At least one dashboard ID must be provided"
|
||||
)
|
||||
@@ -60,14 +58,10 @@ async def migrate_dashboards(
|
||||
)
|
||||
|
||||
if not source_env:
|
||||
logger.error(
|
||||
f"[migrate_dashboards][Coherence:Failed] Source environment not found: {request.source_env_id}"
|
||||
)
|
||||
logger.explore("Source environment not found for migrate_dashboards", payload={"source_env_id": request.source_env_id}, error=f"Source environment not found: {request.source_env_id}")
|
||||
raise HTTPException(status_code=404, detail="Source environment not found")
|
||||
if not target_env:
|
||||
logger.error(
|
||||
f"[migrate_dashboards][Coherence:Failed] Target environment not found: {request.target_env_id}"
|
||||
)
|
||||
logger.explore("Target environment not found for migrate_dashboards", payload={"target_env_id": request.target_env_id}, error=f"Target environment not found: {request.target_env_id}")
|
||||
raise HTTPException(status_code=404, detail="Target environment not found")
|
||||
|
||||
try:
|
||||
@@ -83,16 +77,12 @@ async def migrate_dashboards(
|
||||
plugin_id="superset-migration", params=task_params
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"[migrate_dashboards][Coherence:OK] Migration task created: {task_obj.id} for {len(request.dashboard_ids)} dashboards"
|
||||
)
|
||||
logger.reflect("Migration task created", payload={"task_id": str(task_obj.id), "dashboard_count": len(request.dashboard_ids)})
|
||||
|
||||
return TaskResponse(task_id=str(task_obj.id))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[migrate_dashboards][Coherence:Failed] Failed to create migration task: {e}"
|
||||
)
|
||||
logger.explore("Failed to create migration task", error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=503, detail=f"Failed to create migration task: {e!s}"
|
||||
)
|
||||
@@ -124,9 +114,7 @@ async def backup_dashboards(
|
||||
f"env={request.env_id}, count={len(request.dashboard_ids)}, schedule={request.schedule}",
|
||||
):
|
||||
if not request.dashboard_ids:
|
||||
logger.error(
|
||||
"[backup_dashboards][Coherence:Failed] No dashboard IDs provided"
|
||||
)
|
||||
logger.explore("No dashboard IDs provided for backup_dashboards", error="At least one dashboard ID must be provided")
|
||||
raise HTTPException(
|
||||
status_code=400, detail="At least one dashboard ID must be provided"
|
||||
)
|
||||
@@ -135,9 +123,7 @@ async def backup_dashboards(
|
||||
env = next((e for e in environments if e.id == request.env_id), None)
|
||||
|
||||
if not env:
|
||||
logger.error(
|
||||
f"[backup_dashboards][Coherence:Failed] Environment not found: {request.env_id}"
|
||||
)
|
||||
logger.explore("Environment not found for backup_dashboards", payload={"env_id": request.env_id}, error=f"Environment not found: {request.env_id}")
|
||||
raise HTTPException(status_code=404, detail="Environment not found")
|
||||
|
||||
try:
|
||||
@@ -151,16 +137,12 @@ async def backup_dashboards(
|
||||
plugin_id="superset-backup", params=task_params
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"[backup_dashboards][Coherence:OK] Backup task created: {task_obj.id} for {len(request.dashboard_ids)} dashboards"
|
||||
)
|
||||
logger.reflect("Backup task created", payload={"task_id": str(task_obj.id), "dashboard_count": len(request.dashboard_ids)})
|
||||
|
||||
return TaskResponse(task_id=str(task_obj.id))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[backup_dashboards][Coherence:Failed] Failed to create backup task: {e}"
|
||||
)
|
||||
logger.explore("Failed to create backup task", error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=503, detail=f"Failed to create backup task: {e!s}"
|
||||
)
|
||||
|
||||
@@ -64,14 +64,10 @@ async def get_database_mappings(
|
||||
target_env = next((e for e in environments if e.id == target_env_id), None)
|
||||
|
||||
if not source_env:
|
||||
logger.error(
|
||||
f"[get_database_mappings][Coherence:Failed] Source environment not found: {source_env_id}"
|
||||
)
|
||||
logger.explore("Source environment not found for get_database_mappings", payload={"source_env_id": source_env_id}, error=f"Source environment not found: {source_env_id}")
|
||||
raise HTTPException(status_code=404, detail="Source environment not found")
|
||||
if not target_env:
|
||||
logger.error(
|
||||
f"[get_database_mappings][Coherence:Failed] Target environment not found: {target_env_id}"
|
||||
)
|
||||
logger.explore("Target environment not found for get_database_mappings", payload={"target_env_id": target_env_id}, error=f"Target environment not found: {target_env_id}")
|
||||
raise HTTPException(status_code=404, detail="Target environment not found")
|
||||
|
||||
try:
|
||||
@@ -90,16 +86,12 @@ async def get_database_mappings(
|
||||
for s in suggestions
|
||||
]
|
||||
|
||||
logger.info(
|
||||
f"[get_database_mappings][Coherence:OK] Returning {len(mappings)} database mapping suggestions"
|
||||
)
|
||||
logger.reflect("Returning database mapping suggestions", payload={"count": len(mappings)})
|
||||
|
||||
return DatabaseMappingsResponse(mappings=mappings)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[get_database_mappings][Coherence:Failed] Failed to get database mappings: {e}"
|
||||
)
|
||||
logger.explore("Failed to get database mappings", error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=503, detail=f"Failed to get database mappings: {e!s}"
|
||||
)
|
||||
@@ -127,9 +119,7 @@ async def get_dashboard_detail(
|
||||
environments = config_manager.get_environments()
|
||||
env = next((e for e in environments if e.id == env_id), None)
|
||||
if not env:
|
||||
logger.error(
|
||||
f"[get_dashboard_detail][Coherence:Failed] Environment not found: {env_id}"
|
||||
)
|
||||
logger.explore("Environment not found for get_dashboard_detail", payload={"env_id": env_id}, error=f"Environment not found: {env_id}")
|
||||
raise HTTPException(status_code=404, detail="Environment not found")
|
||||
|
||||
try:
|
||||
@@ -150,16 +140,12 @@ async def get_dashboard_detail(
|
||||
dashboard_ref, client
|
||||
)
|
||||
detail = await client.get_dashboard_detail(dashboard_id)
|
||||
logger.info(
|
||||
f"[get_dashboard_detail][Coherence:OK] Dashboard ref={dashboard_ref} resolved_id={dashboard_id}: {detail.get('chart_count', 0)} charts, {detail.get('dataset_count', 0)} datasets"
|
||||
)
|
||||
logger.reflect("Dashboard detail retrieved", payload={"dashboard_ref": dashboard_ref, "dashboard_id": dashboard_id, "chart_count": detail.get("chart_count", 0), "dataset_count": detail.get("dataset_count", 0)})
|
||||
return DashboardDetailResponse(**detail)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[get_dashboard_detail][Coherence:Failed] Failed to fetch dashboard detail: {e}"
|
||||
)
|
||||
logger.explore("Failed to fetch dashboard detail", payload={"dashboard_ref": dashboard_ref}, error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=503, detail=f"Failed to fetch dashboard detail: {e!s}"
|
||||
)
|
||||
@@ -195,18 +181,14 @@ async def get_dashboard_tasks_history(
|
||||
environments = config_manager.get_environments()
|
||||
env = next((e for e in environments if e.id == env_id), None)
|
||||
if not env:
|
||||
logger.error(
|
||||
f"[get_dashboard_tasks_history][Coherence:Failed] Environment not found: {env_id}"
|
||||
)
|
||||
logger.explore("Environment not found for get_dashboard_tasks_history", payload={"env_id": env_id}, error=f"Environment not found: {env_id}")
|
||||
raise HTTPException(status_code=404, detail="Environment not found")
|
||||
client = SupersetClient(env)
|
||||
dashboard_id = await _resolve_dashboard_id_from_ref_async(
|
||||
dashboard_ref, client
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
"[get_dashboard_tasks_history][Coherence:Failed] Non-numeric dashboard ref requires env_id"
|
||||
)
|
||||
logger.explore("Non-numeric dashboard ref requires env_id", payload={"dashboard_ref": dashboard_ref}, error="Non-numeric dashboard ref requires env_id")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="env_id is required when dashboard reference is a slug",
|
||||
@@ -259,9 +241,7 @@ async def get_dashboard_tasks_history(
|
||||
)
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"[get_dashboard_tasks_history][Coherence:OK] Found {len(items)} tasks for dashboard_ref={dashboard_ref}, dashboard_id={dashboard_id}"
|
||||
)
|
||||
logger.reflect("Dashboard tasks history retrieved", payload={"task_count": len(items), "dashboard_ref": dashboard_ref, "dashboard_id": dashboard_id})
|
||||
return DashboardTaskHistoryResponse(dashboard_id=dashboard_id, items=items)
|
||||
finally:
|
||||
if client is not None:
|
||||
@@ -292,9 +272,7 @@ async def get_dashboard_thumbnail(
|
||||
environments = config_manager.get_environments()
|
||||
env = next((e for e in environments if e.id == env_id), None)
|
||||
if not env:
|
||||
logger.error(
|
||||
f"[get_dashboard_thumbnail][Coherence:Failed] Environment not found: {env_id}"
|
||||
)
|
||||
logger.explore("Environment not found for get_dashboard_thumbnail", payload={"env_id": env_id}, error=f"Environment not found: {env_id}")
|
||||
raise HTTPException(status_code=404, detail="Environment not found")
|
||||
|
||||
try:
|
||||
@@ -338,9 +316,7 @@ async def get_dashboard_thumbnail(
|
||||
if matched:
|
||||
digest = matched.group(1)
|
||||
except DashboardNotFoundError:
|
||||
logger.warning(
|
||||
"[get_dashboard_thumbnail][Fallback] cache_dashboard_screenshot endpoint unavailable, fallback to dashboard.thumbnail_url"
|
||||
)
|
||||
logger.explore("cache_dashboard_screenshot endpoint unavailable, fallback to thumbnail_url", payload={"dashboard_id": dashboard_id}, error="cache_dashboard_screenshot not available")
|
||||
|
||||
if not digest:
|
||||
dashboard_payload = await async_client.request(
|
||||
@@ -392,16 +368,12 @@ async def get_dashboard_thumbnail(
|
||||
content_type = thumb_response.headers.get("Content-Type", "image/png")
|
||||
return Response(content=thumb_response.content, media_type=content_type)
|
||||
except DashboardNotFoundError as e:
|
||||
logger.error(
|
||||
f"[get_dashboard_thumbnail][Coherence:Failed] Dashboard not found for thumbnail: {e}"
|
||||
)
|
||||
logger.explore("Dashboard not found for thumbnail", payload={"dashboard_ref": dashboard_ref}, error=str(e))
|
||||
raise HTTPException(status_code=404, detail="Dashboard thumbnail not found")
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[get_dashboard_thumbnail][Coherence:Failed] Failed to fetch dashboard thumbnail: {e}"
|
||||
)
|
||||
logger.explore("Failed to fetch dashboard thumbnail", payload={"dashboard_ref": dashboard_ref}, error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail=f"Failed to fetch dashboard thumbnail: {e!s}",
|
||||
|
||||
@@ -72,16 +72,16 @@ async def get_dashboards(
|
||||
f"page_context={page_context}, applyp={apply_profile_default}, overhide={override_show_all}",
|
||||
):
|
||||
if page < 1:
|
||||
logger.error(f"[get_dashboards][Coherence:Failed] Invalid page: {page}")
|
||||
logger.explore("Invalid page parameter", payload={"page": page}, error="Page must be >= 1")
|
||||
raise HTTPException(status_code=400, detail="Page must be >= 1")
|
||||
if page_size < 1 or page_size > 100:
|
||||
logger.error(f"[get_dashboards][Coherence:Failed] Invalid page_size: {page_size}")
|
||||
logger.explore("Invalid page_size parameter", payload={"page_size": page_size}, error="Page size must be between 1 and 100")
|
||||
raise HTTPException(status_code=400, detail="Page size must be between 1 and 100")
|
||||
|
||||
environments = config_manager.get_environments()
|
||||
env = next((e for e in environments if e.id == env_id), None)
|
||||
if not env:
|
||||
logger.error(f"[get_dashboards][Coherence:Failed] Environment not found: {env_id}")
|
||||
logger.explore("Environment not found for get_dashboards", payload={"env_id": env_id}, error=f"Environment not found: {env_id}")
|
||||
raise HTTPException(status_code=404, detail="Environment not found")
|
||||
|
||||
bound_username: str | None = None
|
||||
@@ -360,10 +360,13 @@ async def get_dashboards(
|
||||
end_idx = start_idx + page_size
|
||||
paginated_dashboards = dashboards[start_idx:end_idx]
|
||||
|
||||
logger.info(
|
||||
f"[get_dashboards][Coherence:OK] Returning {len(paginated_dashboards)} dashboards "
|
||||
f"(page {page}/{total_pages}, total: {total}, profile_filter_applied={effective_profile_filter.applied})"
|
||||
)
|
||||
logger.reflect("Returning paginated dashboards", payload={
|
||||
"count": len(paginated_dashboards),
|
||||
"page": page,
|
||||
"total_pages": total_pages,
|
||||
"total": total,
|
||||
"profile_filter_applied": effective_profile_filter.applied,
|
||||
})
|
||||
|
||||
response_dashboards = _project_dashboard_response_items(
|
||||
paginated_dashboards
|
||||
@@ -381,9 +384,7 @@ async def get_dashboards(
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[get_dashboards][Coherence:Failed] Failed to fetch dashboards: {e}"
|
||||
)
|
||||
logger.explore("Failed to fetch dashboards", error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=503, detail=f"Failed to fetch dashboards: {e!s}"
|
||||
)
|
||||
|
||||
@@ -168,7 +168,7 @@ async def get_dataset_ids(
|
||||
environments = config_manager.get_environments()
|
||||
env = next((e for e in environments if e.id == env_id), None)
|
||||
if not env:
|
||||
logger.error(f"[get_dataset_ids][Coherence:Failed] Environment not found: {env_id}")
|
||||
logger.explore("Environment not found for get_dataset_ids", payload={"env_id": env_id}, error=f"Environment not found: {env_id}")
|
||||
raise HTTPException(status_code=404, detail="Environment not found")
|
||||
|
||||
try:
|
||||
@@ -188,12 +188,12 @@ async def get_dataset_ids(
|
||||
|
||||
# Extract and return just the IDs
|
||||
dataset_ids = [d['id'] for d in datasets]
|
||||
logger.info(f"[get_dataset_ids][Coherence:OK] Returning {len(dataset_ids)} dataset IDs")
|
||||
logger.reflect("Returning dataset IDs", payload={"count": len(dataset_ids)})
|
||||
|
||||
return {"dataset_ids": dataset_ids}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[get_dataset_ids][Coherence:Failed] Failed to fetch dataset IDs: {e}")
|
||||
logger.explore("Failed to fetch dataset IDs", error=str(e))
|
||||
raise HTTPException(status_code=503, detail=f"Failed to fetch dataset IDs: {e!s}")
|
||||
# #endregion get_dataset_ids
|
||||
|
||||
@@ -222,17 +222,17 @@ async def get_datasets(
|
||||
with belief_scope("get_datasets", f"env_id={env_id}, search={search}, filter={filter}, page={page}, page_size={page_size}"):
|
||||
# Validate pagination parameters
|
||||
if page < 1:
|
||||
logger.error(f"[get_datasets][Coherence:Failed] Invalid page: {page}")
|
||||
logger.explore("Invalid page parameter", payload={"page": page}, error="Page must be >= 1")
|
||||
raise HTTPException(status_code=400, detail="Page must be >= 1")
|
||||
if page_size < 1 or page_size > 100:
|
||||
logger.error(f"[get_datasets][Coherence:Failed] Invalid page_size: {page_size}")
|
||||
logger.explore("Invalid page_size parameter", payload={"page_size": page_size}, error="Page size must be between 1 and 100")
|
||||
raise HTTPException(status_code=400, detail="Page size must be between 1 and 100")
|
||||
|
||||
# Validate environment exists
|
||||
environments = config_manager.get_environments()
|
||||
env = next((e for e in environments if e.id == env_id), None)
|
||||
if not env:
|
||||
logger.error(f"[get_datasets][Coherence:Failed] Environment not found: {env_id}")
|
||||
logger.explore("Environment not found for get_datasets", payload={"env_id": env_id}, error=f"Environment not found: {env_id}")
|
||||
raise HTTPException(status_code=404, detail="Environment not found")
|
||||
|
||||
try:
|
||||
@@ -298,7 +298,7 @@ async def get_datasets(
|
||||
# Slice datasets for current page
|
||||
paginated_datasets = datasets[start_idx:end_idx]
|
||||
|
||||
logger.info(f"[get_datasets][Coherence:OK] Returning {len(paginated_datasets)} datasets (page {page}/{total_pages}, total: {total}, stats: total={stats.total} unmapped={stats.unmapped_count} mapped={stats.mapped_count} linked={stats.linked_count})")
|
||||
logger.reflect("Returning paginated datasets", payload={"count": len(paginated_datasets), "page": page, "total_pages": total_pages, "total": total, "stats": {"total": stats.total, "unmapped": stats.unmapped_count, "mapped": stats.mapped_count, "linked": stats.linked_count}})
|
||||
|
||||
return DatasetsResponse(
|
||||
datasets=paginated_datasets,
|
||||
@@ -310,7 +310,7 @@ async def get_datasets(
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[get_datasets][Coherence:Failed] Failed to fetch datasets: {e}")
|
||||
logger.explore("Failed to fetch datasets", error=str(e))
|
||||
raise HTTPException(status_code=503, detail=f"Failed to fetch datasets: {e!s}")
|
||||
# #endregion get_datasets
|
||||
|
||||
@@ -345,12 +345,12 @@ async def map_columns(
|
||||
with belief_scope("map_columns", f"env={request.env_id}, count={len(request.dataset_ids)}, source={request.source_type}"):
|
||||
# Validate request
|
||||
if not request.dataset_ids:
|
||||
logger.error("[map_columns][Coherence:Failed] No dataset IDs provided")
|
||||
logger.explore("No dataset IDs provided for map_columns", error="At least one dataset ID must be provided")
|
||||
raise HTTPException(status_code=400, detail="At least one dataset ID must be provided")
|
||||
|
||||
# Validate source type
|
||||
if request.source_type not in ['sqllab', 'xlsx']:
|
||||
logger.error(f"[map_columns][Coherence:Failed] Invalid source type: {request.source_type}")
|
||||
logger.explore("Invalid source type for map_columns", payload={"source_type": request.source_type}, error=f"Invalid source type: {request.source_type}")
|
||||
raise HTTPException(status_code=400, detail="Source type must be 'sqllab' or 'xlsx'")
|
||||
|
||||
# Validate database_id for sqllab source
|
||||
@@ -362,7 +362,7 @@ async def map_columns(
|
||||
env = next((e for e in environments if e.id == request.env_id), None)
|
||||
|
||||
if not env:
|
||||
logger.error(f"[map_columns][Coherence:Failed] Environment not found: {request.env_id}")
|
||||
logger.explore("Environment not found for map_columns", payload={"env_id": request.env_id}, error=f"Environment not found: {request.env_id}")
|
||||
raise HTTPException(status_code=404, detail="Environment not found")
|
||||
|
||||
try:
|
||||
@@ -381,12 +381,12 @@ async def map_columns(
|
||||
params=task_params
|
||||
)
|
||||
|
||||
logger.info(f"[map_columns][Coherence:OK] Mapping task created: {task_obj.id} for {len(request.dataset_ids)} datasets")
|
||||
logger.reflect("Mapping task created", payload={"task_id": str(task_obj.id), "dataset_count": len(request.dataset_ids)})
|
||||
|
||||
return TaskResponse(task_id=str(task_obj.id))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[map_columns][Coherence:Failed] Failed to create mapping task: {e}")
|
||||
logger.explore("Failed to create mapping task", error=str(e))
|
||||
raise HTTPException(status_code=503, detail=f"Failed to create mapping task: {e!s}")
|
||||
# #endregion map_columns
|
||||
|
||||
@@ -419,7 +419,7 @@ async def generate_docs(
|
||||
with belief_scope("generate_docs", f"env={request.env_id}, count={len(request.dataset_ids)}, provider={request.llm_provider}"):
|
||||
# Validate request
|
||||
if not request.dataset_ids:
|
||||
logger.error("[generate_docs][Coherence:Failed] No dataset IDs provided")
|
||||
logger.explore("No dataset IDs provided for generate_docs", error="At least one dataset ID must be provided")
|
||||
raise HTTPException(status_code=400, detail="At least one dataset ID must be provided")
|
||||
|
||||
# Validate environment exists
|
||||
@@ -427,7 +427,7 @@ async def generate_docs(
|
||||
env = next((e for e in environments if e.id == request.env_id), None)
|
||||
|
||||
if not env:
|
||||
logger.error(f"[generate_docs][Coherence:Failed] Environment not found: {request.env_id}")
|
||||
logger.explore("Environment not found for generate_docs", payload={"env_id": request.env_id}, error=f"Environment not found: {request.env_id}")
|
||||
raise HTTPException(status_code=404, detail="Environment not found")
|
||||
|
||||
try:
|
||||
@@ -444,12 +444,12 @@ async def generate_docs(
|
||||
params=task_params
|
||||
)
|
||||
|
||||
logger.info(f"[generate_docs][Coherence:OK] Documentation generation task created: {task_obj.id} for {len(request.dataset_ids)} datasets")
|
||||
logger.reflect("Documentation generation task created", payload={"task_id": str(task_obj.id), "dataset_count": len(request.dataset_ids)})
|
||||
|
||||
return TaskResponse(task_id=str(task_obj.id))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[generate_docs][Coherence:Failed] Failed to create documentation generation task: {e}")
|
||||
logger.explore("Failed to create documentation generation task", error=str(e))
|
||||
raise HTTPException(status_code=503, detail=f"Failed to create documentation generation task: {e!s}")
|
||||
# #endregion generate_docs
|
||||
|
||||
@@ -472,7 +472,7 @@ async def get_dataset_detail(
|
||||
environments = config_manager.get_environments()
|
||||
env = next((e for e in environments if e.id == env_id), None)
|
||||
if not env:
|
||||
logger.error(f"[get_dataset_detail][Coherence:Failed] Environment not found: {env_id}")
|
||||
logger.explore("Environment not found for get_dataset_detail", payload={"env_id": env_id}, error=f"Environment not found: {env_id}")
|
||||
raise HTTPException(status_code=404, detail="Environment not found")
|
||||
|
||||
try:
|
||||
@@ -485,12 +485,12 @@ async def get_dataset_detail(
|
||||
db_obj = dataset_detail["database"]
|
||||
dataset_detail["database"] = db_obj.get("database_name", str(db_obj.get("id", "")))
|
||||
|
||||
logger.info(f"[get_dataset_detail][Coherence:OK] Retrieved dataset {dataset_id} with {dataset_detail['column_count']} columns and {dataset_detail['linked_dashboard_count']} linked dashboards")
|
||||
logger.reflect("Retrieved dataset detail", payload={"dataset_id": dataset_id, "column_count": dataset_detail["column_count"], "linked_dashboard_count": dataset_detail["linked_dashboard_count"]})
|
||||
|
||||
return DatasetDetailResponse(**dataset_detail)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[get_dataset_detail][Coherence:Failed] Failed to fetch dataset detail: {e}")
|
||||
logger.explore("Failed to fetch dataset detail", payload={"dataset_id": dataset_id}, error=str(e))
|
||||
raise HTTPException(status_code=503, detail=f"Failed to fetch dataset detail: {e!s}")
|
||||
# #endregion get_dataset_detail
|
||||
|
||||
@@ -504,7 +504,7 @@ def _strip_html_tags(text: str) -> str:
|
||||
return text
|
||||
if re.search(r"<[^>]*>", text):
|
||||
cleaned = re.sub(r"<[^>]*>", "", text)
|
||||
logger.warning("[_strip_html_tags] HTML tags detected and stripped from description")
|
||||
logger.explore("HTML tags detected and stripped from description", payload={"function": "_strip_html_tags"})
|
||||
return cleaned
|
||||
return text
|
||||
# #endregion _strip_html_tags
|
||||
@@ -534,14 +534,14 @@ async def update_column_description(
|
||||
# Validate and sanitize description
|
||||
description = _strip_html_tags(body.description)
|
||||
if len(description) > 2000:
|
||||
logger.error(f"[update_column_description][Coherence:Failed] Description exceeds 2000 chars: {len(description)}")
|
||||
logger.explore("Description exceeds maximum length for update_column_description", payload={"length": len(description)}, error="Description must not exceed 2000 characters")
|
||||
raise HTTPException(status_code=400, detail="Description must not exceed 2000 characters")
|
||||
|
||||
# Validate environment
|
||||
environments = config_manager.get_environments()
|
||||
env = next((e for e in environments if e.id == env_id), None)
|
||||
if not env:
|
||||
logger.error(f"[update_column_description][Coherence:Failed] Environment not found: {env_id}")
|
||||
logger.explore("Environment not found for update_column_description", payload={"env_id": env_id}, error=f"Environment not found: {env_id}")
|
||||
raise HTTPException(status_code=404, detail="Environment not found")
|
||||
|
||||
try:
|
||||
@@ -553,7 +553,7 @@ async def update_column_description(
|
||||
try:
|
||||
response = await client.get_dataset(dataset_id)
|
||||
except Exception as e:
|
||||
logger.error(f"[update_column_description][Coherence:Failed] Superset GET failed: {e}")
|
||||
logger.explore("Superset GET request failed for update_column_description", payload={"dataset_id": dataset_id}, error=str(e))
|
||||
raise HTTPException(status_code=502, detail=f"Failed to fetch dataset from Superset: {e!s}")
|
||||
|
||||
if isinstance(response, dict) and "result" in response:
|
||||
@@ -562,7 +562,7 @@ async def update_column_description(
|
||||
dataset = response
|
||||
|
||||
if not dataset or not dataset.get("id"):
|
||||
logger.error(f"[update_column_description][Coherence:Failed] Dataset {dataset_id} not found")
|
||||
logger.explore("Dataset not found for update_column_description", payload={"dataset_id": dataset_id}, error=f"Dataset {dataset_id} not found")
|
||||
raise HTTPException(status_code=404, detail=f"Dataset {dataset_id} not found")
|
||||
|
||||
# Find and modify the target column
|
||||
@@ -575,7 +575,7 @@ async def update_column_description(
|
||||
break
|
||||
|
||||
if not found:
|
||||
logger.error(f"[update_column_description][Coherence:Failed] Column {column_id} not found in dataset {dataset_id}")
|
||||
logger.explore("Column not found for update_column_description", payload={"column_id": column_id, "dataset_id": dataset_id}, error=f"Column {column_id} not found")
|
||||
raise HTTPException(status_code=404, detail=f"Column {column_id} not found in dataset {dataset_id}")
|
||||
|
||||
# PUT back with override_columns=false
|
||||
@@ -583,19 +583,16 @@ async def update_column_description(
|
||||
try:
|
||||
await client.update_dataset(dataset_id, put_payload, override_columns=False)
|
||||
except Exception as e:
|
||||
logger.error(f"[update_column_description][Coherence:Failed] Superset PUT failed: {e}")
|
||||
logger.explore("Superset PUT request failed for update_column_description", payload={"dataset_id": dataset_id}, error=str(e))
|
||||
raise HTTPException(status_code=502, detail=f"Failed to update dataset in Superset: {e!s}")
|
||||
|
||||
logger.reflect(
|
||||
f"Updated column {column_id} description for dataset {dataset_id}",
|
||||
extra={"src": "update_column_description"},
|
||||
)
|
||||
logger.reflect("Column description updated", payload={"column_id": column_id, "dataset_id": dataset_id})
|
||||
return {"success": True, "description": description}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"[update_column_description][Coherence:Failed] Unexpected error: {e}")
|
||||
logger.explore("Unexpected error in update_column_description", payload={"dataset_id": dataset_id, "column_id": column_id}, error=str(e))
|
||||
raise HTTPException(status_code=500, detail=f"Unexpected error: {e!s}")
|
||||
# #endregion update_column_description
|
||||
|
||||
@@ -622,14 +619,14 @@ async def update_metric_description(
|
||||
# Validate and sanitize description
|
||||
description = _strip_html_tags(body.description)
|
||||
if len(description) > 2000:
|
||||
logger.error(f"[update_metric_description][Coherence:Failed] Description exceeds 2000 chars: {len(description)}")
|
||||
logger.explore("Description exceeds maximum length for update_metric_description", payload={"length": len(description)}, error="Description must not exceed 2000 characters")
|
||||
raise HTTPException(status_code=400, detail="Description must not exceed 2000 characters")
|
||||
|
||||
# Validate environment
|
||||
environments = config_manager.get_environments()
|
||||
env = next((e for e in environments if e.id == env_id), None)
|
||||
if not env:
|
||||
logger.error(f"[update_metric_description][Coherence:Failed] Environment not found: {env_id}")
|
||||
logger.explore("Environment not found for update_metric_description", payload={"env_id": env_id}, error=f"Environment not found: {env_id}")
|
||||
raise HTTPException(status_code=404, detail="Environment not found")
|
||||
|
||||
try:
|
||||
@@ -641,7 +638,7 @@ async def update_metric_description(
|
||||
try:
|
||||
response = await client.get_dataset(dataset_id)
|
||||
except Exception as e:
|
||||
logger.error(f"[update_metric_description][Coherence:Failed] Superset GET failed: {e}")
|
||||
logger.explore("Superset GET request failed for update_metric_description", payload={"dataset_id": dataset_id}, error=str(e))
|
||||
raise HTTPException(status_code=502, detail=f"Failed to fetch dataset from Superset: {e!s}")
|
||||
|
||||
if isinstance(response, dict) and "result" in response:
|
||||
@@ -650,7 +647,7 @@ async def update_metric_description(
|
||||
dataset = response
|
||||
|
||||
if not dataset or not dataset.get("id"):
|
||||
logger.error(f"[update_metric_description][Coherence:Failed] Dataset {dataset_id} not found")
|
||||
logger.explore("Dataset not found for update_metric_description", payload={"dataset_id": dataset_id}, error=f"Dataset {dataset_id} not found")
|
||||
raise HTTPException(status_code=404, detail=f"Dataset {dataset_id} not found")
|
||||
|
||||
# Find and modify the target metric
|
||||
@@ -663,26 +660,23 @@ async def update_metric_description(
|
||||
break
|
||||
|
||||
if not found:
|
||||
logger.error(f"[update_metric_description][Coherence:Failed] Metric {metric_id} not found in dataset {dataset_id}")
|
||||
logger.explore("Metric not found for update_metric_description", payload={"metric_id": metric_id, "dataset_id": dataset_id}, error=f"Metric {metric_id} not found")
|
||||
raise HTTPException(status_code=404, detail=f"Metric {metric_id} not found in dataset {dataset_id}")
|
||||
|
||||
# PUT back with override_columns=false
|
||||
try:
|
||||
await client.update_dataset(dataset_id, dataset, override_columns=False)
|
||||
except Exception as e:
|
||||
logger.error(f"[update_metric_description][Coherence:Failed] Superset PUT failed: {e}")
|
||||
logger.explore("Superset PUT request failed for update_metric_description", payload={"dataset_id": dataset_id}, error=str(e))
|
||||
raise HTTPException(status_code=502, detail=f"Failed to update dataset in Superset: {e!s}")
|
||||
|
||||
logger.reflect(
|
||||
f"Updated metric {metric_id} description for dataset {dataset_id}",
|
||||
extra={"src": "update_metric_description"},
|
||||
)
|
||||
logger.reflect("Metric description updated", payload={"metric_id": metric_id, "dataset_id": dataset_id})
|
||||
return {"success": True, "description": description}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"[update_metric_description][Coherence:Failed] Unexpected error: {e}")
|
||||
logger.explore("Unexpected error in update_metric_description", payload={"dataset_id": dataset_id, "metric_id": metric_id}, error=str(e))
|
||||
raise HTTPException(status_code=500, detail=f"Unexpected error: {e!s}")
|
||||
# #endregion update_metric_description
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ def _build_no_repo_status_payload() -> dict:
|
||||
# @PRE `error` is a non-HTTPException instance.
|
||||
# @POST Raises HTTPException(500) with route-specific context.
|
||||
def _handle_unexpected_git_route_error(route_name: str, error: Exception) -> None:
|
||||
logger.error(f"[{route_name}][Coherence:Failed] {error}")
|
||||
logger.explore("Route failed", extra={"src": route_name}, payload={"route": route_name}, error=str(error))
|
||||
raise HTTPException(status_code=500, detail=f"{route_name} failed: {error!s}")
|
||||
# #endregion _handle_unexpected_git_route_error
|
||||
|
||||
|
||||
@@ -205,9 +205,7 @@ async def get_repository_status_batch(
|
||||
"sync_status": "ERROR",
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[get_repository_status_batch][Coherence:Failed] Failed for dashboard {dashboard_id}: {e}"
|
||||
)
|
||||
logger.explore("Failed for dashboard in batch status", extra={"src": "get_repository_status_batch"}, payload={"dashboard_id": dashboard_id}, error=str(e))
|
||||
statuses[str(dashboard_id)] = {
|
||||
**_build_no_repo_status_payload(),
|
||||
"sync_state": "ERROR",
|
||||
|
||||
@@ -95,15 +95,11 @@ async def init_repository(
|
||||
db_repo.current_branch = "dev"
|
||||
|
||||
db.commit()
|
||||
logger.info(
|
||||
f"[init_repository][Coherence:OK] Repository initialized for dashboard {dashboard_id}"
|
||||
)
|
||||
logger.reflect("Repository initialized for dashboard", extra={"src": "init_repository"}, payload={"dashboard_id": dashboard_id})
|
||||
return {"status": "success", "message": "Repository initialized"}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(
|
||||
f"[init_repository][Coherence:Failed] Failed to init repository: {e}"
|
||||
)
|
||||
logger.explore("Failed to init repository", extra={"src": "init_repository"}, error=str(e))
|
||||
if isinstance(e, HTTPException):
|
||||
raise
|
||||
_handle_unexpected_git_route_error("init_repository", e)
|
||||
|
||||
@@ -248,8 +248,9 @@ async def add_environment(
|
||||
try:
|
||||
await _validate_superset_connection_fast(env)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[add_environment][Coherence:Failed] Connection validation failed: {e}"
|
||||
logger.explore(
|
||||
"Connection validation failed",
|
||||
error=str(e),
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=400, detail=f"Connection validation failed: {e}"
|
||||
@@ -291,8 +292,10 @@ async def update_environment(
|
||||
try:
|
||||
await _validate_superset_connection_fast(env_to_validate)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[update_environment][Coherence:Failed] Connection validation failed: {e}"
|
||||
logger.explore(
|
||||
"Connection validation failed",
|
||||
error=str(e),
|
||||
payload={"environment_id": id},
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=400, detail=f"Connection validation failed: {e}"
|
||||
@@ -344,13 +347,16 @@ async def test_environment_connection(
|
||||
try:
|
||||
await _validate_superset_connection_fast(env)
|
||||
|
||||
logger.info(
|
||||
f"[test_environment_connection][Coherence:OK] Connection successful for {id}"
|
||||
logger.reflect(
|
||||
"Connection test successful",
|
||||
payload={"environment_id": id},
|
||||
)
|
||||
return {"status": "success", "message": "Connection successful"}
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[test_environment_connection][Coherence:Failed] Connection failed for {id}: {e}"
|
||||
logger.explore(
|
||||
"Connection test failed",
|
||||
error=str(e),
|
||||
payload={"environment_id": id},
|
||||
)
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user