Datavision / backend /main.py
DataVision CI/CD Bot
release: clean production build for HuggingFace Space
09801ca
Raw
History Blame Contribute Delete
21.8 kB
import os
import sys
import time
import asyncio
# Force UTF-8 for Windows console emoji printing
if sys.stdout.encoding != 'utf-8':
try:
sys.stdout.reconfigure(encoding='utf-8')
except Exception:
pass
os.environ["USE_TF"] = "0"
os.environ["USE_TORCH"] = "1"
os.environ["TF_USE_LEGACY_KERAS"] = "1"
import uvicorn
import logging
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from dotenv import load_dotenv
from pathlib import Path
from starlette.middleware.sessions import SessionMiddleware
# Configure Logging
# Configure Logging
# Note: Basic logging is handled in settings.py which is imported below.
# We just ensure uvicorn loggers verify settings.
import sys
logging.getLogger("uvicorn.access").handlers = [logging.StreamHandler(sys.stdout)]
logging.getLogger("uvicorn.error").handlers = [logging.StreamHandler(sys.stdout)]
logger = logging.getLogger("main")
# Load env using the new Settings logic (it loads env automatically on import)
from config.settings import Settings
# Import Routers
from api.v1.endpoints import (
chat,
files,
analytics,
reports,
email_prefs,
decisions,
synthetic,
anomalies,
lineage,
collaboration,
simulator,
search,
voice,
ws,
live_streaming
)
# Import Autonomous Dashboard API
from api.v1.endpoints import dashboard_api
# ๐Ÿง  Import Autonomous Brain API
from api.v1.endpoints import brain
logger.info("โœ… Autonomous Brain API loaded")
# Initialize App
app = FastAPI(
title="DataVision - Autonomous AI Data Platform",
version="3.0.0",
docs_url="/docs",
redoc_url="/redoc",
openapi_url="/openapi.json",
)
@app.on_event("startup")
async def verify_tables():
from database.db import engine
from database import orm
from database.orm import Base
from sqlalchemy import text
try:
async with engine.begin() as conn:
logger.info("๐Ÿ“ฆ Verifying database tables exist (auto-healing)...")
await conn.run_sync(Base.metadata.create_all)
await conn.execute(text("ALTER TABLE IF EXISTS api_keys ADD COLUMN IF NOT EXISTS api_key VARCHAR(255) NOT NULL DEFAULT ''"))
await conn.execute(text("ALTER TABLE IF EXISTS api_keys ADD COLUMN IF NOT EXISTS status VARCHAR(20) NOT NULL DEFAULT 'active'"))
await conn.execute(text("ALTER TABLE IF EXISTS api_keys ADD COLUMN IF NOT EXISTS data_processed_mb DOUBLE PRECISION NOT NULL DEFAULT 0"))
await conn.execute(text("ALTER TABLE IF EXISTS chat_channels ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()"))
await conn.execute(text("ALTER TABLE IF EXISTS channel_messages ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()"))
await conn.execute(text("ALTER TABLE IF EXISTS message_reactions ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()"))
await conn.execute(text("ALTER TABLE IF EXISTS message_reactions ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()"))
await conn.execute(text("ALTER TABLE IF EXISTS api_call_logs ADD COLUMN IF NOT EXISTS http_method VARCHAR(10) NOT NULL DEFAULT 'GET'"))
await conn.execute(text("ALTER TABLE IF EXISTS api_call_logs ADD COLUMN IF NOT EXISTS response_time_ms INTEGER NOT NULL DEFAULT 0"))
await conn.execute(text("""DO $$ BEGIN
IF to_regclass('public.webhook_endpoints') IS NOT NULL
AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'webhook_endpoints' AND column_name = 'secret')
AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'webhook_endpoints' AND column_name = 'secret_key') THEN
ALTER TABLE webhook_endpoints RENAME COLUMN secret TO secret_key;
END IF;
END $$;"""))
logger.info("โœ… Database tables verified/created successfully!")
except Exception as e:
logger.error(f"โš ๏ธ Failed to auto-heal database tables: {e}")
# ============================================
# SECURITY MIDDLEWARE
# ============================================
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
"""Add security headers to all responses"""
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
# Security headers
response.headers["X-Content-Type-Options"] = "nosniff"
# Allow embedding in HuggingFace iframe
response.headers["X-Frame-Options"] = "ALLOWALL"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
# Remove server header (MutableHeaders uses del, not pop)
if "server" in response.headers:
del response.headers["server"]
return response
# Add security headers middleware
app.add_middleware(SecurityHeadersMiddleware)
# Add session middleware (required for Authlib OAuth)
# For Hugging Face Spaces (which uses an iframe), same_site must be "none" and https_only=True to prevent dropping the session cookie
session_secret = os.environ.get("JWT_SECRET") or os.environ.get("JWT_SECRET_KEY") or "datavision-production-jwt-secret-key-32bytes-long!"
is_prod = bool(os.environ.get("SPACE_HOST"))
app.add_middleware(
SessionMiddleware,
secret_key=session_secret,
same_site="none" if is_prod else "lax",
https_only=is_prod
)
class APICallLogMiddleware(BaseHTTPMiddleware):
"""Log all API requests to the APICallLog table"""
async def dispatch(self, request: Request, call_next):
start_time = time.time()
response = await call_next(request)
process_time_ms = int((time.time() - start_time) * 1000)
# Log all /api/ requests, ignoring /api/config and static files
if request.url.path.startswith("/api/") and not request.url.path.startswith("/api/config"):
asyncio.create_task(self.log_request(
endpoint=request.url.path,
method=request.method,
status_code=response.status_code,
latency_ms=process_time_ms,
user_id=request.headers.get("X-User-ID")
))
return response
async def log_request(self, endpoint: str, method: str, status_code: int, latency_ms: int, user_id: str):
try:
from database.db import AsyncSessionLocal
from database.orm import APICallLog
import uuid
parsed_uid = None
if user_id:
try:
parsed_uid = uuid.UUID(user_id)
except ValueError:
parsed_uid = None
async with AsyncSessionLocal() as db:
log = APICallLog(
user_id=parsed_uid,
endpoint=endpoint,
http_method=method,
status_code=status_code,
response_time_ms=latency_ms
)
db.add(log)
await db.commit()
except Exception as e:
logger.error(f"Failed to log API call: {e}")
app.add_middleware(APICallLogMiddleware)
# Rate Limit Header Middleware
from core.rate_limiter import RateLimitHeaderMiddleware, get_rate_limiter
app.add_middleware(RateLimitHeaderMiddleware)
logger.info(f"โœ… Rate limiter: {type(get_rate_limiter()).__name__}")
# Trusted host middleware (prevent host header injection)
# ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
# if os.environ.get("ENVIRONMENT") == "production":
# app.add_middleware(TrustedHostMiddleware, allowed_hosts=ALLOWED_HOSTS)
# CORS - Secure configuration
# Allow HuggingFace Spaces domains and local development
ALLOWED_ORIGINS = os.environ.get(
"CORS_ORIGINS",
"http://localhost:5173,http://localhost:5174,http://localhost:3000,http://localhost:8000,https://huggingface.co,https://killerkumar-ai-business-analyst.hf.space,https://datavision-ai-datavision.hf.space,https://*.hf.space"
).split(",")
# For HuggingFace Spaces, we need to allow all origins for the embedded iframe
if os.path.exists("/app"): # Running in Docker/HF
ALLOWED_ORIGINS = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"], # Allow all headers for HF compatibility
max_age=600,
)
# Mount Static Files
static_dir = os.path.join(os.path.dirname(__file__), "static")
os.makedirs(static_dir, exist_ok=True)
app.mount("/static", StaticFiles(directory=static_dir), name="static")
app.include_router(chat.router, prefix="/api/v1/chat", tags=["Chat"])
app.include_router(files.router, prefix="/api/v1/files", tags=["Files"])
app.include_router(analytics.router, prefix="/api/v1/analytics", tags=["Analytics"])
app.include_router(reports.router, prefix="/api/v1/reports", tags=["Reports"])
app.include_router(email_prefs.router, prefix="/api/v1/settings", tags=["Settings"])
app.include_router(decisions.router, prefix="/api/v1/decisions", tags=["Decisions"])
app.include_router(synthetic.router, prefix="/api/v1/synthetic", tags=["Synthetic Data"])
# Mount new feature APIs
app.include_router(anomalies.router, prefix="/api/v1", tags=["Anomalies"])
app.include_router(lineage.router, prefix="/api/v1/lineage", tags=["Lineage"])
app.include_router(collaboration.router, prefix="/api/v1/collaboration", tags=["Collaboration"])
app.include_router(simulator.router, prefix="/api/v1", tags=["Simulator"])
app.include_router(search.router, prefix="/api/v1", tags=["Search"])
app.include_router(voice.router, prefix="/api/v1/voice", tags=["Voice"])
app.include_router(ws.router, prefix="/api/v1/ws", tags=["WebSockets"])
app.include_router(live_streaming.router, prefix="/api/v1", tags=["Live Streaming"])
from api.v1.endpoints import auth, admin
app.include_router(auth.router, prefix="/api/v1/auth", tags=["Auth"])
app.include_router(admin.router, prefix="/api/v1/admin", tags=["Admin"])
# ๐Ÿ“ค Exports API (PDF, PPTX, Email)
from api.v1.endpoints import exports
app.include_router(exports.router, prefix="/api/v1", tags=["Exports"])
logger.info("โœ… Exports API loaded (PDF, PPTX, Email)")
# ๐Ÿ† Autonomous Dashboard API
app.include_router(dashboard_api.router, prefix="/api/v1/dashboard", tags=["Dashboard"])
# ๐Ÿง  Autonomous Brain API
app.include_router(brain.router, prefix="/api/v1/brain", tags=["Brain"])
# ๐Ÿš€ DataVision API v2 (All Features)
from api.v1.endpoints import datavision_api
app.include_router(datavision_api.router, prefix="/api/v2", tags=["DataVision v2"])
logger.info("โœ… DataVision API v2 loaded")
# ๐Ÿค– AutoML API (Production ML)
from api.v1.endpoints import automl_api
app.include_router(automl_api.router, prefix="/api/v2/automl", tags=["AutoML"])
app.include_router(automl_api.router, prefix="/api/v1/automl", tags=["AutoML v1"]) # Also register at v1 for compatibility
logger.info("โœ… AutoML API loaded")
# ๐Ÿค– Autonomous API (Model Management + Auto-Fix)
from api.v1.endpoints import autonomous_api
app.include_router(autonomous_api.router, prefix="/api/v2/autonomous", tags=["Autonomous"])
logger.info("โœ… Autonomous API loaded (Model Management + Auto-Fix)")
# ๐Ÿค– Agentic AutoML (Multi-Agent ML Pipeline)
from api.v1.endpoints import agentic_automl_api
app.include_router(agentic_automl_api.router, prefix="/api/v2", tags=["Agentic AutoML"])
logger.info("โœ… Agentic AutoML API loaded (9 Specialized Agents)")
# ๐Ÿฅ Data Health API
from api.v1.endpoints import data_health_api
app.include_router(data_health_api.router, prefix="/api/v1", tags=["Data Health"])
logger.info("โœ… Data Health API loaded")
# ๐ŸŽฎ Playground API
from api.v1.endpoints import playground_api
app.include_router(playground_api.router, prefix="/api/v1", tags=["Playground"])
logger.info("โœ… Playground API loaded")
# ๐Ÿ” Explainability API
from api.v1.endpoints import explainability_api
app.include_router(explainability_api.router, prefix="/api/v1", tags=["Explainability"])
logger.info("โœ… Explainability API loaded")
# ๐Ÿšข Enterprise MLOps Deployment API
from api.v1.endpoints import mlops
app.include_router(mlops.router, prefix="/api/v1", tags=["MLOps"])
logger.info("โœ… MLOps API loaded")
# ๐ŸŽฏ Clustering API (Unsupervised Learning)
from api.v1.endpoints import clustering_api
app.include_router(clustering_api.router, prefix="/api/v1/ml", tags=["Clustering"])
logger.info("โœ… Clustering API loaded (K-Means, DBSCAN, GMM, Spectral)")
# ๐Ÿ’ป Web IDE API (Execution & Chat)
from api.v1.endpoints import ide_api
app.include_router(ide_api.router, prefix="/api/v1/ide", tags=["Web IDE"])
logger.info("โœ… Web IDE API loaded (Code Execution)")
# ๐Ÿง  Agentic Autopilot API (V5 โ€” Autonomous Data Science)
# โš™๏ธ Autopilot & Agents
from api.v1.endpoints import autopilot_api
app.include_router(autopilot_api.router, prefix="/api/v1/autopilot", tags=["Autopilot"])
# ๐Ÿ›  Developer & API Center
from api.v1.endpoints import developer
app.include_router(developer.router, prefix="/api/v1/developer", tags=["Developer"])
# ๐Ÿš€ Model Deploy API
from api.v1.endpoints import deploy
app.include_router(deploy.router, prefix="/api/v1", tags=["Deploy"])
# ๐Ÿšจ Anomaly Detection
from api.v1.endpoints import anomalies
app.include_router(anomalies.router, prefix="/api/v1/anomalies", tags=["Anomalies"])
# ๐Ÿ‘๏ธ Computer Vision
from api.v1.endpoints import cv_router
app.include_router(cv_router.router, prefix="/api/v1/cv", tags=["Computer Vision"])
# ๐Ÿง  Vector AI & RAG Inspector
from api.v1.endpoints import vector_router
app.include_router(vector_router.router, prefix="/api/v1/vector", tags=["Vector AI"])
# ==========================================
# ๐Ÿš€ V2 APIs (Agentic + RAG)
# ==========================================
from fastapi.responses import FileResponse
# Frontend static directory (pre-built React app)
frontend_static_dir = "/app/static" if os.path.exists("/app/static") else os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "frontend", "dist")
@app.get("/api/config")
async def get_frontend_config():
"""
Secure config endpoint for frontend.
Returns only PUBLIC configuration needed for client-side operations.
"""
return {
"VITE_API_URL": os.environ.get("VITE_API_URL", ""),
"auth_mode": "jwt"
}
@app.get("/api")
async def api_info():
"""API information endpoint"""
return {
"platform": "DataVision - Universal AI Data Platform",
"version": "2.0.0",
"status": "active",
"apis": {
"v1": "/api/v1/* (legacy endpoints)",
"v2": "/api/v2/* (all features)"
},
"capabilities": [
"autonomous_brain",
"universal_agent",
"chain_of_thought_reasoning",
"react_framework",
"knowledge_graphs",
"predictive_intelligence",
"root_cause_analysis",
"ai_segmentation",
"trend_detection",
"cohort_analysis",
"automl",
"what_if_simulation",
"enterprise_exports"
]
}
# Serve React frontend - must be after all API routes
@app.get("/")
async def serve_frontend():
"""Serve React frontend"""
index_path = os.path.join(frontend_static_dir, "index.html")
if os.path.exists(index_path):
return FileResponse(index_path)
return {"error": "Frontend not found", "path": index_path}
# Serve logo files
@app.get("/logo.svg")
async def serve_logo_svg():
logo_path = os.path.join(frontend_static_dir, "logo.svg")
if os.path.exists(logo_path):
return FileResponse(logo_path, media_type="image/svg+xml")
return {"error": "logo.svg not found"}
@app.get("/logo.png")
async def serve_logo_png():
logo_path = os.path.join(frontend_static_dir, "logo.png")
if os.path.exists(logo_path):
return FileResponse(logo_path)
return {"error": "logo.png not found"}
@app.get("/logo.jpg")
async def serve_logo_jpg():
logo_path = os.path.join(frontend_static_dir, "logo.jpg")
if os.path.exists(logo_path):
return FileResponse(logo_path)
return {"error": "logo.jpg not found"}
@app.get("/datavision-logo.jpg")
async def serve_datavision_logo():
logo_path = os.path.join(frontend_static_dir, "datavision-logo.jpg")
if os.path.exists(logo_path):
return FileResponse(logo_path, media_type="image/jpeg")
return {"error": "datavision-logo.jpg not found"}
@app.get("/datavision-logo-dark.jpg")
async def serve_datavision_logo_dark():
logo_path = os.path.join(frontend_static_dir, "datavision-logo-dark.jpg")
if os.path.exists(logo_path):
return FileResponse(logo_path, media_type="image/jpeg")
return {"error": "datavision-logo-dark.jpg not found"}
@app.get("/datavision-logo-light.jpg")
async def serve_datavision_logo_light():
logo_path = os.path.join(frontend_static_dir, "datavision-logo-light.jpg")
if os.path.exists(logo_path):
return FileResponse(logo_path, media_type="image/jpeg")
return {"error": "datavision-logo-light.jpg not found"}
@app.get("/logo_transparent.png")
async def serve_logo_transparent():
logo_path = os.path.join(frontend_static_dir, "logo_transparent.png")
if os.path.exists(logo_path):
return FileResponse(logo_path)
return {"error": "logo_transparent.png not found"}
@app.get("/datavision_icon_v3.png")
async def serve_datavision_icon():
icon_path = os.path.join(frontend_static_dir, "datavision_icon_v3.png")
if os.path.exists(icon_path):
return FileResponse(icon_path)
return {"error": "datavision_icon_v3.png not found"}
# Serve service worker
@app.get("/sw.js")
async def serve_sw():
return FileResponse(os.path.join(frontend_static_dir, "sw.js"))
# Mount static assets (JS, CSS, images)
if os.path.exists(frontend_static_dir):
assets_dir = os.path.join(frontend_static_dir, "assets")
if os.path.exists(assets_dir):
app.mount("/assets", StaticFiles(directory=assets_dir), name="frontend-assets")
frontend_public_dir = os.path.join(os.path.dirname(__file__), "..", "frontend", "public")
# Catch-all route for SPA (React Router) & static files - must be LAST
@app.get("/{full_path:path}")
async def spa_fallback(full_path: str):
"""Fallback for client-side routing and static images"""
# Don't catch API routes or FastAPI built-in docs
if full_path.startswith("api/") or full_path.startswith("docs") or full_path.startswith("redoc") or full_path.startswith("openapi.json"):
return {"error": "Not found"}
# 1. Check if direct file exists in frontend static dir (dist)
dist_file = os.path.join(frontend_static_dir, full_path)
if os.path.isfile(dist_file):
return FileResponse(dist_file)
# 2. Check if file exists in frontend public dir
public_file = os.path.join(frontend_public_dir, full_path)
if os.path.isfile(public_file):
return FileResponse(public_file)
# 3. Otherwise serve index.html for SPA client-side routing
index_path = os.path.join(frontend_static_dir, "index.html")
if os.path.exists(index_path):
return FileResponse(index_path)
return {"error": "Frontend not found"}
@app.on_event("startup")
async def startup_event():
logger.info("=" * 50)
logger.info("๐Ÿš€ DATAVISION 2.0 - Universal AI Data Platform")
logger.info("=" * 50)
logger.info("๐Ÿง  Autonomous Brain: ACTIVE")
logger.info("๐Ÿค– Universal Agent: ACTIVE")
logger.info("๐Ÿ”— Knowledge Graphs: ACTIVE")
logger.info("๐Ÿ”ฎ Predictive Intelligence: ACTIVE")
logger.info("โšก Advanced MCPs: LOADED")
logger.info("๐Ÿข Enterprise Features: ENABLED")
# ๐Ÿ“ง Start Email Scheduler for Daily/Weekly Reports
try:
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from scheduler.scheduled_reporter import check_and_send_reports
scheduler = AsyncIOScheduler()
# Run every minute to check for scheduled email times
scheduler.add_job(
check_and_send_reports,
CronTrigger(minute="*"), # Every minute
id="email_report_checker",
name="Check and send scheduled email reports",
replace_existing=True
)
scheduler.start()
logger.info("๐Ÿ“ง Email Scheduler: ACTIVE (checking every minute)")
except Exception as e:
logger.warning(f"โš ๏ธ Email scheduler not started: {e}")
# ๐Ÿ”Œ Start WebSocket real-time updates task
try:
from api.v1.endpoints.ws import push_realtime_updates
import asyncio
asyncio.create_task(push_realtime_updates())
logger.info("๐Ÿ”Œ WebSocket real-time updates: ACTIVE")
except Exception as e:
logger.warning(f"โš ๏ธ WebSocket task not started: {e}")
logger.info("=" * 50)
logger.info("๐Ÿ“ก API v2 available at: /api/v2")
logger.info("๐Ÿ“š Docs at: /docs")
logger.info("=" * 50)
@app.on_event("shutdown")
async def shutdown_event():
logger.info("๐Ÿ›‘ DataVision Shutting Down...")
if __name__ == "__main__":
import os
port = int(os.environ.get("PORT", 8000))
uvicorn.run("main:app", host="0.0.0.0", port=port, reload=False)