feat: add import routes and ingestion service for project and scenario imports

This commit is contained in:
2025-11-10 09:28:32 +01:00
parent eaef99f0ac
commit 609b0d779f
3 changed files with 155 additions and 0 deletions

143
routes/imports.py Normal file
View File

@@ -0,0 +1,143 @@
from __future__ import annotations
from io import BytesIO
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from dependencies import get_import_ingestion_service, require_roles
from models import User
from schemas.imports import (
ImportCommitRequest,
ProjectImportCommitResponse,
ProjectImportPreviewResponse,
ScenarioImportCommitResponse,
ScenarioImportPreviewResponse,
)
from services.importers import ImportIngestionService, UnsupportedImportFormat
router = APIRouter(prefix="/imports", tags=["Imports"])
MANAGE_ROLES = ("project_manager", "admin")
async def _read_upload_file(upload: UploadFile) -> BytesIO:
content = await upload.read()
if not content:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Uploaded file is empty.",
)
return BytesIO(content)
@router.post(
"/projects/preview",
response_model=ProjectImportPreviewResponse,
status_code=status.HTTP_200_OK,
)
async def preview_project_import(
file: UploadFile = File(...,
description="Project import file (CSV or Excel)"),
_: User = Depends(require_roles(*MANAGE_ROLES)),
ingestion_service: ImportIngestionService = Depends(
get_import_ingestion_service),
) -> ProjectImportPreviewResponse:
if not file.filename:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Filename is required for import.",
)
stream = await _read_upload_file(file)
try:
preview = ingestion_service.preview_projects(stream, file.filename)
except UnsupportedImportFormat as exc:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(exc),
) from exc
return ProjectImportPreviewResponse.model_validate(preview)
@router.post(
"/scenarios/preview",
response_model=ScenarioImportPreviewResponse,
status_code=status.HTTP_200_OK,
)
async def preview_scenario_import(
file: UploadFile = File(...,
description="Scenario import file (CSV or Excel)"),
_: User = Depends(require_roles(*MANAGE_ROLES)),
ingestion_service: ImportIngestionService = Depends(
get_import_ingestion_service),
) -> ScenarioImportPreviewResponse:
if not file.filename:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Filename is required for import.",
)
stream = await _read_upload_file(file)
try:
preview = ingestion_service.preview_scenarios(stream, file.filename)
except UnsupportedImportFormat as exc:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(exc),
) from exc
return ScenarioImportPreviewResponse.model_validate(preview)
def _value_error_status(exc: ValueError) -> int:
detail = str(exc)
if detail.lower().startswith("unknown"):
return status.HTTP_404_NOT_FOUND
return status.HTTP_400_BAD_REQUEST
@router.post(
"/projects/commit",
response_model=ProjectImportCommitResponse,
status_code=status.HTTP_200_OK,
)
async def commit_project_import_endpoint(
payload: ImportCommitRequest,
_: User = Depends(require_roles(*MANAGE_ROLES)),
ingestion_service: ImportIngestionService = Depends(
get_import_ingestion_service),
) -> ProjectImportCommitResponse:
try:
result = ingestion_service.commit_project_import(payload.token)
except ValueError as exc:
raise HTTPException(
status_code=_value_error_status(exc),
detail=str(exc),
) from exc
return ProjectImportCommitResponse.model_validate(result)
@router.post(
"/scenarios/commit",
response_model=ScenarioImportCommitResponse,
status_code=status.HTTP_200_OK,
)
async def commit_scenario_import_endpoint(
payload: ImportCommitRequest,
_: User = Depends(require_roles(*MANAGE_ROLES)),
ingestion_service: ImportIngestionService = Depends(
get_import_ingestion_service),
) -> ScenarioImportCommitResponse:
try:
result = ingestion_service.commit_scenario_import(payload.token)
except ValueError as exc:
raise HTTPException(
status_code=_value_error_status(exc),
detail=str(exc),
) from exc
return ScenarioImportCommitResponse.model_validate(result)