Spaces:
Running
Running
| from fastapi import APIRouter, HTTPException, Depends | |
| from typing import Dict, Any, List | |
| from pydantic import BaseModel | |
| import uuid | |
| import logging | |
| from api.deps import get_current_user_id | |
| logger = logging.getLogger(__name__) | |
| router = APIRouter(prefix="/pipelines", tags=["Pipelines"]) | |
| class PipelineNode(BaseModel): | |
| id: str | |
| type: str | |
| data: Dict[str, Any] | |
| class PipelineEdge(BaseModel): | |
| id: str | |
| source: str | |
| target: str | |
| class PipelineExecutionRequest(BaseModel): | |
| nodes: List[PipelineNode] | |
| edges: List[PipelineEdge] | |
| async def execute_pipeline( | |
| req: PipelineExecutionRequest, | |
| user_id: str = Depends(get_current_user_id) | |
| ): | |
| """ | |
| Executes a visual pipeline represented as a DAG. | |
| This is a mocked execution engine for demonstration purposes, | |
| but it validates the flow logic. | |
| """ | |
| try: | |
| if not req.nodes: | |
| raise HTTPException(status_code=400, detail="Pipeline contains no nodes") | |
| # Build execution graph | |
| graph = {node.id: {"type": node.type, "data": node.data, "next": []} for node in req.nodes} | |
| for edge in req.edges: | |
| if edge.source in graph and edge.target in graph: | |
| graph[edge.source]["next"].append(edge.target) | |
| # Find start node (DataSource) | |
| start_nodes = [n_id for n_id, n_data in graph.items() if n_data["type"] == 'dataSource'] | |
| if not start_nodes: | |
| raise HTTPException(status_code=400, detail="Pipeline must start with a Data Source node") | |
| # Linear execution for demonstration | |
| execution_log = [] | |
| current = start_nodes[0] | |
| while current: | |
| node = graph[current] | |
| action_desc = f"Executing {node['type']}" | |
| if node['type'] == 'dataSource': | |
| file_name = node['data'].get('file', 'unknown_file') | |
| action_desc = f"Loading dataset: {file_name}" | |
| elif node['type'] == 'cleanData': | |
| action_desc = "Cleaning dataset (imputing missing values)" | |
| elif node['type'] == 'transform': | |
| action_desc = "Transforming features (scaling, encoding)" | |
| elif node['type'] == 'trainModel': | |
| action_desc = "Training AutoML Model" | |
| execution_log.append(f"[SUCCESS] {action_desc}") | |
| if node['next']: | |
| current = node['next'][0] # Take first branch for simplicity | |
| else: | |
| current = None | |
| return { | |
| "success": True, | |
| "message": "Pipeline executed successfully", | |
| "log": execution_log, | |
| "pipeline_id": f"pl_{uuid.uuid4().hex[:8]}" | |
| } | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| logger.error(f"Pipeline execution failed: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| from database.db import get_db | |
| from sqlalchemy.ext.asyncio import AsyncSession | |
| from sqlalchemy.future import select | |
| from database.orm import VisualPipeline | |
| async def save_pipeline( | |
| req: PipelineExecutionRequest, | |
| name: str = 'Untitled Pipeline', | |
| user_id: str = Depends(get_current_user_id), | |
| db: AsyncSession = Depends(get_db) | |
| ): | |
| try: | |
| new_pipeline = VisualPipeline( | |
| user_id=user_id, | |
| name=name, | |
| nodes=[n.dict() for n in req.nodes], | |
| edges=[e.dict() for e in req.edges], | |
| is_active=True | |
| ) | |
| db.add(new_pipeline) | |
| await db.commit() | |
| return {'success': True, 'message': 'Pipeline saved', 'pipeline_id': str(new_pipeline.id)} | |
| except Exception as e: | |
| await db.rollback() | |
| return {'success': False, 'error': str(e)} | |
| async def get_saved_pipelines( | |
| user_id: str = Depends(get_current_user_id), | |
| db: AsyncSession = Depends(get_db) | |
| ): | |
| try: | |
| stmt = select(VisualPipeline).where(VisualPipeline.user_id == user_id) | |
| result = await db.execute(stmt) | |
| pipelines = result.scalars().all() | |
| return { | |
| 'success': True, | |
| 'pipelines': [{'id': str(p.id), 'name': p.name, 'created_at': p.created_at.isoformat()} for p in pipelines] | |
| } | |
| except Exception as e: | |
| return {'success': False, 'error': str(e)} | |