backend-api/pipeline/routers/pipelines.py
2025-05-12 21:36:41 +07:00

201 lines
6.6 KiB
Python

from typing import List, Dict
from uuid import UUID
from fastapi import (
APIRouter,
Depends,
HTTPException,
status,
BackgroundTasks,
)
from models.pipeline import Pipeline, PipelineCreate, PipelineStatus
from services.pipeline_service import PipelineService
from dependencies import (
get_pipeline_service,
)
router = APIRouter(
prefix="/pipelines",
tags=["Pipelines"],
responses={404: {"description": "Pipeline not found"}},
)
@router.post(
"/",
response_model=Pipeline,
status_code=status.HTTP_201_CREATED,
summary="Create a new pipeline",
description="Creates a new pipeline configuration and schedules its first run if applicable. The pipeline starts in an INACTIVE state.",
)
async def create_pipeline(
pipeline_in: PipelineCreate,
service: PipelineService = Depends(get_pipeline_service),
) -> Pipeline:
"""
Creates a new data integration pipeline.
- **name**: A unique name for the pipeline.
- **description**: A brief description of the pipeline's purpose.
- **config**: Configuration details including:
- **ingestor_config**: Settings for the data ingestion sources.
- **run_frequency**: How often the pipeline should run (daily, weekly, monthly).
"""
try:
# The service already handles calculating next_run and notifying scheduler
created_pipeline = await service.create_pipeline(
name=pipeline_in.name,
description=pipeline_in.description,
ingestor_config=pipeline_in.config.ingestor_config,
run_frequency=pipeline_in.config.run_frequency,
)
return created_pipeline
except Exception as e:
# Catch potential exceptions during creation (e.g., store errors)
# Log the error ideally
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to create pipeline: {e}",
)
@router.get(
"/",
response_model=List[Pipeline],
summary="List all pipelines",
description="Retrieves a list of all configured pipelines.",
)
async def list_pipelines(
service: PipelineService = Depends(get_pipeline_service),
) -> List[Pipeline]:
"""
Returns a list of all pipelines currently stored.
"""
return await service.list_pipelines()
@router.get(
"/{pipeline_id}",
response_model=Pipeline,
summary="Get a specific pipeline",
description="Retrieves the details of a single pipeline by its unique ID.",
)
async def get_pipeline(
pipeline_id: UUID,
service: PipelineService = Depends(get_pipeline_service),
) -> Pipeline:
"""
Fetches a pipeline by its UUID. Returns 404 if not found.
"""
pipeline = await service.get_pipeline(pipeline_id)
if pipeline is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Pipeline with id {pipeline_id} not found",
)
return pipeline
@router.put(
"/{pipeline_id}",
response_model=Pipeline,
summary="Update a pipeline",
description="Updates the configuration of an existing pipeline. If the run frequency changes, the schedule will be updated.",
)
async def update_pipeline(
pipeline_id: UUID,
pipeline_in: PipelineCreate,
service: PipelineService = Depends(get_pipeline_service),
) -> Pipeline:
"""
Updates an existing pipeline identified by its UUID.
Allows modification of name, description, and configuration (including run frequency).
Returns 404 if the pipeline does not exist.
"""
updated_pipeline = await service.update_pipeline(pipeline_id, pipeline_in)
if updated_pipeline is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Pipeline with id {pipeline_id} not found",
)
return updated_pipeline
@router.delete(
"/{pipeline_id}",
status_code=status.HTTP_204_NO_CONTENT,
summary="Delete a pipeline",
description="Deletes a pipeline configuration and unschedules any future runs.",
)
async def delete_pipeline(
pipeline_id: UUID,
service: PipelineService = Depends(get_pipeline_service),
) -> None:
"""
Deletes a pipeline by its UUID.
Returns 204 No Content on successful deletion.
Returns 404 if the pipeline does not exist.
"""
# Check existence first for a clearer 404
existing_pipeline = await service.get_pipeline(pipeline_id)
if not existing_pipeline:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Pipeline with id {pipeline_id} not found",
)
deleted = await service.delete_pipeline(pipeline_id)
# Service's delete handles scheduler notification
if not deleted:
# This might happen in a race condition or if delete fails after get passes
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Pipeline with id {pipeline_id} not found or could not be deleted.",
)
# No return body needed for 204
@router.post(
"/{pipeline_id}/run",
status_code=status.HTTP_202_ACCEPTED,
response_model=Dict[str, str],
summary="Manually trigger a pipeline run",
description="Initiates an immediate run of the specified pipeline in the background. The pipeline status will be updated during and after the run.",
)
async def run_pipeline_manually(
pipeline_id: UUID,
background_tasks: BackgroundTasks,
service: PipelineService = Depends(get_pipeline_service),
) -> Dict[str, str]:
"""
Triggers a pipeline run asynchronously.
- Checks if the pipeline exists.
- Adds the `service.run_pipeline` task to be executed in the background.
- Returns immediately with a confirmation message.
Returns 404 if the pipeline does not exist.
The service layer handles checks for already active pipelines.
"""
pipeline = await service.get_pipeline(pipeline_id)
if pipeline is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Pipeline with id {pipeline_id} not found, cannot trigger run.",
)
# Optional: Check status here for a quicker response if already active,
# although the service layer also checks this.
if pipeline.status == PipelineStatus.ACTIVE:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, # 409 Conflict is suitable here
detail=f"Pipeline {pipeline_id} is already running.",
)
# Add the potentially long-running task to the background
background_tasks.add_task(service.run_pipeline, pipeline_id=pipeline_id)
return {"detail": f"Pipeline run triggered for {pipeline_id}"}