v1.0
All checks were successful
Build and Deploy Docker Image / build-and-push (push) Successful in 5m5s

working basic setup

- added profile picture upload
- fied a lot of errors with rabbitmq
- updated the "module" tables
- added experimental runner
This commit is contained in:
2026-05-24 22:15:30 +03:00
parent ea4d91f3bd
commit a571ef9c7a
19 changed files with 1776 additions and 329 deletions

View File

@@ -1,10 +1,20 @@
import json
from datetime import datetime
from typing import List
from typing import List, Optional
from connections.db import get_db
from connections.keycloak import (
get_current_token_payload,
)
from connections.minio import (
ensure_bucket_exists,
get_file_from_minio,
upload_file_to_minio,
)
from connections.rabbitmq import (
bind_queues_to_team_exchange,
publish_task_to_team,
)
from crud.experiment_crud import (
create_experiment,
create_experiment_type,
@@ -13,49 +23,141 @@ from crud.experiment_crud import (
delete_instance,
get_all_experiment_types,
get_experiment_instances,
get_experiment_type_by_id,
get_single_experiment,
get_single_instance,
get_user_experiments,
set_simulation_result,
update_experiment,
update_experiment_type,
update_instance,
)
from crud.team_crud import check_team_permission
from fastapi import Depends, Query
# Get team's systems to find max qubits available
from crud.team_crud import (
check_team_permission,
get_team, # You'll need to import this
)
from fastapi import Depends, File, Query, UploadFile
from fastapi.param_functions import Form
from fastapi.routing import APIRouter
from rest_models.experiment_models import (
CreateExperimentRequest,
CreateExperimentResponse,
CreateExperimentTypeRequest,
CreateExperimentTypeResponse,
CreateInstanceRequest,
CreateInstanceResponse,
ExperimentData,
ExperimentListResponse,
ExperimentTypeData,
ExperimentTypeList,
InstanceData,
InstanceListResponse,
SimpleInstanceData,
SimulationResultData,
StartExperimentRequest,
StartExperimentResponse,
UpdateExperimentRequest,
UpdateExperimentResponse,
UpdateInstanceRequest,
UpdateInstanceResponse,
)
from rest_models.machine_models import (
ComputationalSystemShortData,
)
from rest_models.team_models import (
TeamsShortListResponse,
)
from sqlalchemy.ext.asyncio import AsyncSession
from starlette.exceptions import HTTPException
from starlette.responses import StreamingResponse
from starlette.status import HTTP_200_OK
router = APIRouter(tags=["Experiments"])
# ============= HELPER FUNC ===========================
#
async def validate_instance_qubits(
db: AsyncSession, team_id: int, qubits_needed: int
) -> None:
"""
Validate that qubits_needed is within allowed range for the team.
Args:
db: Database session
team_id: Team ID to check systems for
qubits_needed: Number of qubits needed for the instance
Raises:
HTTPException: If validation fails
"""
# Check qubits is positive
if qubits_needed <= 0:
raise HTTPException(
400,
f"Invalid qubits_needed: {qubits_needed}. Qubits must be greater than 0.",
)
team_systems = await get_team(db, team_id)
if not team_systems:
raise HTTPException(
400,
"Team has no computational systems configured. "
"Cannot validate qubits requirement.",
)
max_qubits = max(system.qubits_given for system in team_systems.team_systems)
if qubits_needed > max_qubits:
raise HTTPException(
400,
f"Instance requires {qubits_needed} qubits, but the maximum available "
f"in team's systems is {max_qubits} qubits.",
)
def get_experiment_collective_status(simulation_statuses: list[str]) -> str:
"""
Get the collective status of an experiment based on its simulation statuses.
Args:
simulation_statuses: List of simulation status strings (e.g., ["DRAFT", "COMPLETE", ...])
Returns:
Collective experiment status: "DRAFT", "PROCESSING", "IN QUEUE", or "COMPLETE"
"""
if not simulation_statuses:
return "DRAFT"
# Check if all are DRAFT (or not set/empty)
if all(status == "DRAFT" for status in simulation_statuses):
return "DRAFT"
# Check if any is PROCESSING
if any(
status == "PROCESSING" or status == "IN SYSTEM"
for status in simulation_statuses
):
return "RUNNING"
# Check if any is IN QUEUE (and none are PROCESSING)
if any(status == "IN QUEUE" for status in simulation_statuses):
return "IN QUEUE"
# If all are COMPLETE or ERROR
if all(status in ["COMPLETE", "ERROR"] for status in simulation_statuses):
if any(status == "ERROR" for status in simulation_statuses):
return "COMPLETE WITH ERROR"
return "COMPLETE"
# Default fallback (should not happen with valid statuses)
return "DRAFT"
# ============= EXPERIMENT TYPE ENDPOINTS =============
@router.get("/types", response_model=List[ExperimentTypeData])
@router.get("/types", response_model=List[ExperimentTypeList])
async def get_experiment_types_request(
db: AsyncSession = Depends(get_db),
payload: dict = Depends(get_current_token_payload),
) -> List[ExperimentTypeData]:
) -> List[ExperimentTypeList]:
"""Get all existing experiment types"""
keycloak_id = payload.get("sub")
@@ -65,13 +167,10 @@ async def get_experiment_types_request(
experiment_types = await get_all_experiment_types(db)
return [
ExperimentTypeData(
ExperimentTypeList(
id=exp_type.id,
name=exp_type.name,
description=exp_type.description,
file_frontend=exp_type.file_frontend,
server_path=exp_type.server_path,
file_comp_system=exp_type.file_comp_system,
)
for exp_type in experiment_types
]
@@ -79,46 +178,184 @@ async def get_experiment_types_request(
@router.post("/types", response_model=CreateExperimentTypeResponse)
async def create_experiment_type_request(
create_data: CreateExperimentTypeRequest,
name: str = Form(),
file_frontend: UploadFile = File(...),
file_comp_system: UploadFile = File(...),
description: Optional[str] = None,
db: AsyncSession = Depends(get_db),
payload: dict = Depends(get_current_token_payload),
) -> CreateExperimentTypeResponse:
"""Create a new experiment type (admin only)"""
keycloak_id = payload.get("sub")
"""Create a new experiment type with file uploads to MinIO (admin only)"""
# Check authentication
keycloak_id = payload.get("sub")
if not keycloak_id:
raise HTTPException(403, "Permission denied")
raise HTTPException(status_code=403, detail="Permission denied")
# TODO: Add admin permission check here if needed
# Ensure MinIO bucket exists
await ensure_bucket_exists()
# First create the experiment type in DB to get ID
experiment_type = await create_experiment_type(
db=db,
name=create_data.name,
file_frontend=create_data.file_frontend,
server_path=create_data.server_path,
file_comp_system=create_data.file_comp_system,
description=create_data.description,
name=name,
description=description,
file_frontend="", # Temporary, will update
file_comp_system="", # Temporary, will update
)
try:
# Upload files using the new ID
frontend_path = await upload_file_to_minio(
file=file_frontend, experiment_type_id=experiment_type.id
)
comp_system_path = await upload_file_to_minio(
file=file_comp_system, experiment_type_id=experiment_type.id
)
# Update experiment type with file paths
experiment_type.file_frontend = frontend_path
experiment_type.file_comp_system = comp_system_path
await db.commit()
await db.refresh(experiment_type)
return CreateExperimentTypeResponse(
id=experiment_type.id,
name=experiment_type.name,
description=experiment_type.description,
)
except Exception as e:
# Rollback if upload fails
await db.rollback()
raise HTTPException(
500,
detail=f"Failed to create experiment type: {str(e)}",
)
@router.put("/types", response_model=CreateExperimentTypeResponse)
async def update_type_request(
type_id: int = Form(),
name: Optional[str] = Form(None),
file_frontend: Optional[UploadFile] | None = File(None),
file_comp_system: Optional[UploadFile] | None = File(None),
description: Optional[str] = None,
db: AsyncSession = Depends(get_db),
payload: dict = Depends(get_current_token_payload),
) -> CreateExperimentTypeResponse:
"""Create a new experiment type with file uploads to MinIO (admin only)"""
# Check authentication
keycloak_id = payload.get("sub")
if not keycloak_id:
raise HTTPException(status_code=403, detail="Permission denied")
# Ensure MinIO bucket exists
await ensure_bucket_exists()
frontend_path = None
comp_path = None
if file_frontend:
frontend_path = await upload_file_to_minio(
file=file_frontend, experiment_type_id=type_id
)
if file_comp_system:
comp_path = await upload_file_to_minio(
file=file_comp_system, experiment_type_id=type_id
)
# First create the experiment type in DB to get ID
experiment_type = await update_experiment_type(
db=db,
id=type_id,
name=name,
description=description,
file_frontend=frontend_path, # Temporary, will update
file_comp_system=comp_path, # Temporary, will update
)
return CreateExperimentTypeResponse(
id=experiment_type.id,
name=experiment_type.name,
description=experiment_type.description,
file_frontend=experiment_type.file_frontend,
server_path=experiment_type.server_path,
file_comp_system=experiment_type.file_comp_system,
)
@router.get("/types/frontend")
async def get_frontend_file(
experiment_type_id: int = Query(),
db: AsyncSession = Depends(get_db),
payload: dict = Depends(get_current_token_payload),
):
"""Get the frontend HTML file"""
# Get experiment type from database
experiment_type = await get_experiment_type_by_id(db, experiment_type_id)
if not experiment_type:
raise HTTPException(status_code=404, detail="Experiment type not found")
if not experiment_type.file_frontend:
raise HTTPException(status_code=404, detail="Frontend file not found")
# Get file from MinIO
file_response = await get_file_from_minio(experiment_type.file_frontend)
# Return file as streaming response with correct content type
return StreamingResponse(
file_response,
media_type="text/html",
headers={
"Content-Disposition": f"inline; filename=frontend_{experiment_type_id}.html"
},
)
@router.get("/types/comp-system")
async def get_comp_system_file(
experiment_type_id: int,
db: AsyncSession = Depends(get_db),
payload: dict = Depends(get_current_token_payload),
):
"""Get the comp system Python file"""
# Get experiment type from database
experiment_type = await get_experiment_type_by_id(db, experiment_type_id)
if not experiment_type:
raise HTTPException(status_code=404, detail="Experiment type not found")
if not experiment_type.file_comp_system:
raise HTTPException(status_code=404, detail="Comp system file not found")
# Get file from MinIO
file_response = await get_file_from_minio(experiment_type.file_comp_system)
# Return Python file for download (since it will be imported dynamically)
return StreamingResponse(
file_response,
media_type="text/x-python",
headers={
"Content-Disposition": f"attachment; filename=comp_system_{experiment_type_id}.py"
},
)
# ============= EXPERIMENT ENDPOINTS =============
@router.post("", response_model=CreateExperimentResponse)
@router.post("", response_model=ExperimentData)
async def create_experiment_request(
create_data: CreateExperimentRequest,
db: AsyncSession = Depends(get_db),
payload: dict = Depends(get_current_token_payload),
) -> CreateExperimentResponse:
) -> ExperimentData:
"""Create a new experiment"""
keycloak_id = payload.get("sub")
@@ -138,13 +375,44 @@ async def create_experiment_request(
description=create_data.description,
)
return CreateExperimentResponse(
return ExperimentData(
id=experiment.id,
team_id=experiment.team_id,
experiment_type_id=experiment.experiment_type_id,
team=TeamsShortListResponse(
team_id=experiment.team.id, team_name=experiment.team.name
),
name=experiment.name,
description=experiment.description,
created_at=experiment.created_at,
experiment_type=ExperimentTypeList(
id=experiment.experiment_type.id,
name=experiment.experiment_type.name,
description=experiment.experiment_type.description,
),
instances_count=0,
status=get_experiment_collective_status(
list(
map(
lambda x: (
x.simulation_result.status.name
if x.simulation_result
else "DRAFT"
),
experiment.instances,
)
)
),
instance_preview=list(
map(
lambda inst: SimpleInstanceData(
id=inst.id,
instance_data={},
name=inst.name,
qubits_needed=inst.qubits_needed,
description=inst.description,
),
experiment.instances[:6],
)
),
)
@@ -169,25 +437,51 @@ async def get_experiment_request(
return ExperimentData(
id=experiment.id,
team_id=experiment.team_id,
team_name=experiment.team.name if experiment.team else None,
experiment_type_id=experiment.experiment_type_id,
experiment_type_name=experiment.experiment_type.name
if experiment.experiment_type
else None,
team=TeamsShortListResponse(
team_id=experiment.team.id, team_name=experiment.team.name
),
name=experiment.name,
description=experiment.description,
created_at=experiment.created_at,
instances_count=len(experiment.instances) if experiment.instances else 0,
experiment_type=ExperimentTypeList(
id=experiment.experiment_type.id,
name=experiment.experiment_type.name,
description=experiment.experiment_type.description,
),
instances_count=len(experiment.instances),
status=get_experiment_collective_status(
list(
map(
lambda x: (
x.simulation_result.status.name
if x.simulation_result
else "DRAFT"
),
experiment.instances,
)
)
),
instance_preview=list(
map(
lambda inst: SimpleInstanceData(
id=inst.id,
instance_data="{}",
name=inst.name,
qubits_needed=inst.qubits_needed,
description=inst.description,
),
experiment.instances[:6],
)
),
)
@router.put("", response_model=UpdateExperimentResponse)
@router.put("", response_model=ExperimentData)
async def update_experiment_request(
update_data: UpdateExperimentRequest,
db: AsyncSession = Depends(get_db),
payload: dict = Depends(get_current_token_payload),
) -> UpdateExperimentResponse:
) -> ExperimentData:
"""Update experiment data (cannot update type)"""
keycloak_id = payload.get("sub")
@@ -210,13 +504,44 @@ async def update_experiment_request(
description=update_data.description,
)
return UpdateExperimentResponse(
return ExperimentData(
id=updated_experiment.id,
team_id=updated_experiment.team_id,
experiment_type_id=updated_experiment.experiment_type_id,
team=TeamsShortListResponse(
team_id=updated_experiment.team.id, team_name=updated_experiment.team.name
),
name=updated_experiment.name,
description=updated_experiment.description,
created_at=updated_experiment.created_at,
experiment_type=ExperimentTypeList(
id=updated_experiment.experiment_type.id,
name=updated_experiment.experiment_type.name,
description=updated_experiment.experiment_type.description,
),
instances_count=len(experiment.instances),
status=get_experiment_collective_status(
list(
map(
lambda x: (
x.simulation_result.status.name
if x.simulation_result
else "DRAFT"
),
experiment.instances,
)
)
),
instance_preview=list(
map(
lambda inst: SimpleInstanceData(
id=inst.id,
instance_data=json.dumps(inst.instance_data),
name=inst.name,
qubits_needed=inst.qubits_needed,
description=inst.description,
),
updated_experiment.instances[:6],
)
),
)
@@ -244,18 +569,42 @@ async def get_user_experiments_request(
result.append(
ExperimentData(
id=experiment.id,
team_id=experiment.team_id,
team_name=experiment.team.name if experiment.team else None,
experiment_type_id=experiment.experiment_type_id,
experiment_type_name=experiment.experiment_type.name
if experiment.experiment_type
else None,
team=TeamsShortListResponse(
team_id=experiment.team.id, team_name=experiment.team.name
),
name=experiment.name,
description=experiment.description,
created_at=experiment.created_at,
instances_count=len(experiment.instances)
if experiment.instances
else 0,
experiment_type=ExperimentTypeList(
id=experiment.experiment_type.id,
name=experiment.experiment_type.name,
description=experiment.experiment_type.description,
),
instances_count=len(experiment.instances),
status=get_experiment_collective_status(
list(
map(
lambda x: (
x.simulation_result.status.name
if x.simulation_result
else "DRAFT"
),
experiment.instances,
)
)
),
instance_preview=list(
map(
lambda inst: SimpleInstanceData(
id=inst.id,
instance_data="{}",
name=inst.name,
qubits_needed=inst.qubits_needed,
description=inst.description,
),
experiment.instances[:6],
)
),
)
)
@@ -299,12 +648,12 @@ async def delete_experiment_request(
# ============= INSTANCE ENDPOINTS =============
@router.post("/instance", response_model=CreateInstanceResponse)
@router.post("/instance", response_model=SimpleInstanceData)
async def create_instance_request(
create_data: CreateInstanceRequest,
db: AsyncSession = Depends(get_db),
payload: dict = Depends(get_current_token_payload),
) -> CreateInstanceResponse:
) -> SimpleInstanceData:
"""Add an instance to an experiment"""
keycloak_id = payload.get("sub")
@@ -323,27 +672,26 @@ async def create_instance_request(
instance = await create_instance(
db=db,
experiment_id=create_data.experiment_id,
instance_data_id=create_data.instance_data_id,
instance_data=create_data.instance_data,
name=create_data.name,
description=create_data.description,
)
return CreateInstanceResponse(
return SimpleInstanceData(
id=instance.id,
experiment_id=instance.experiment_id,
instance_data_id=instance.instance_data_id,
instance_data=json.dumps(create_data.instance_data),
name=instance.name,
qubits_needed=instance.qubits_needed,
description=instance.description,
simulation_result_id=instance.simulation_result_id,
)
@router.put("/instance", response_model=UpdateInstanceResponse)
@router.put("/instance", response_model=SimpleInstanceData)
async def update_instance_request(
update_data: UpdateInstanceRequest,
db: AsyncSession = Depends(get_db),
payload: dict = Depends(get_current_token_payload),
) -> UpdateInstanceResponse:
) -> SimpleInstanceData:
"""Update instance data (cannot update instance_data_id)"""
keycloak_id = payload.get("sub")
@@ -364,20 +712,26 @@ async def update_instance_request(
["manage_experiments"], experiment.team_id, db, keycloak_id
)
if update_data.qubits_needed is not None:
await validate_instance_qubits(
db, experiment.team_id, update_data.qubits_needed
)
updated_instance = await update_instance(
db=db,
instance_id=update_data.instance_id,
name=update_data.name,
description=update_data.description,
instance_data=update_data.instance_data,
qubits_needed=update_data.qubits_needed,
)
return UpdateInstanceResponse(
return SimpleInstanceData(
id=updated_instance.id,
experiment_id=updated_instance.experiment_id,
instance_data_id=updated_instance.instance_data_id,
instance_data=json.dumps(updated_instance.instance_data),
name=updated_instance.name,
qubits_needed=updated_instance.qubits_needed,
description=updated_instance.description,
simulation_result_id=updated_instance.simulation_result_id,
)
@@ -412,19 +766,26 @@ async def get_experiment_instances_request(
for instance in instances:
result.append(
InstanceData(
id=instance.id,
experiment_id=instance.experiment_id,
instance_data_id=instance.instance_data_id,
instance_id=instance.id,
instance_data=json.dumps(instance.instance_data),
name=instance.name,
qubits_needed=instance.qubits_needed,
description=instance.description,
simulation_result_id=instance.simulation_result_id,
simulation_status=instance.simulation_result.status.name
if instance.simulation_result and instance.simulation_result.status
else None,
simulation_started_at=instance.simulation_result.started_at
if instance.simulation_result
else None,
simulation_ended_at=instance.simulation_result.ended_at
simulation_result=SimulationResultData(
id=instance.simulation_result.id,
comp_system=ComputationalSystemShortData(
system_id=instance.simulation_result.computational_system.id,
system_name=instance.simulation_result.computational_system.system_name,
)
if instance.simulation_result.computational_system
else None,
simulation_result=json.dumps(
instance.simulation_result.simulation_result
),
status=instance.simulation_result.status.name,
started_at=instance.simulation_result.started_at,
ended_at=instance.simulation_result.ended_at,
)
if instance.simulation_result
else None,
)
@@ -472,61 +833,7 @@ async def delete_instance_request(
return {"message": "Instance deleted successfully"}
# ============= SIMULATION ENDPOINTS =============
@router.post("/start", response_model=StartExperimentResponse)
async def start_experiment_request(
start_data: StartExperimentRequest,
db: AsyncSession = Depends(get_db),
payload: dict = Depends(get_current_token_payload),
) -> StartExperimentResponse:
"""Start an experiment (create simulation result for an instance)"""
keycloak_id = payload.get("sub")
if not keycloak_id:
raise HTTPException(403, "Permission denied")
# Get instance to find its experiment
instance = await get_single_instance(db, start_data.instance_id)
if not instance:
raise HTTPException(404, "Instance not found")
experiment = await get_single_experiment(db, instance.experiment_id)
if not experiment:
raise HTTPException(404, "Experiment not found")
# Check if user has permission to run experiments in this team
await check_team_permission(
["run_experiments"], experiment.team_id, db, keycloak_id
)
# Create or update simulation result
simulation_result = await set_simulation_result(
db=db,
comp_system_id=start_data.comp_system_id,
simulation_result_id=start_data.simulation_result_id,
status_name="PENDING", # Start with PENDING status
started_at=datetime.now(),
)
# Link simulation result to instance if not already linked
if not instance.simulation_result_id:
await update_instance(
db=db,
instance_id=instance.id,
simulation_result_id=simulation_result.id,
)
return StartExperimentResponse(
instance_id=instance.id,
simulation_result_id=simulation_result.id,
status="PENDING",
started_at=simulation_result.started_at,
)
@router.get("/instance", response_model=InstanceData)
@router.get("/instance/id", response_model=InstanceData)
async def get_instance_request(
instance_id: int = Query(),
db: AsyncSession = Depends(get_db),
@@ -551,19 +858,111 @@ async def get_instance_request(
await check_team_permission([], experiment.team_id, db, keycloak_id)
return InstanceData(
id=instance.id,
experiment_id=instance.experiment_id,
instance_data_id=instance.instance_data_id,
instance_id=instance.id,
instance_data=json.dumps(instance.instance_data),
name=instance.name,
description=instance.description,
simulation_result_id=instance.simulation_result_id,
simulation_status=instance.simulation_result.status.name
if instance.simulation_result and instance.simulation_result.status
else None,
simulation_started_at=instance.simulation_result.started_at
if instance.simulation_result
else None,
simulation_ended_at=instance.simulation_result.ended_at
qubits_needed=instance.qubits_needed,
simulation_result=SimulationResultData(
id=instance.simulation_result.id,
comp_system=ComputationalSystemShortData(
system_id=instance.simulation_result.computational_system.id,
system_name=instance.simulation_result.computational_system.system_name,
)
if instance.simulation_result.computational_system
else None,
simulation_result=json.dumps(instance.simulation_result.simulation_result),
status=instance.simulation_result.status.name,
started_at=instance.simulation_result.started_at,
ended_at=instance.simulation_result.ended_at,
)
if instance.simulation_result
else None,
)
# ============= SIMULATION ENDPOINTS =============
@router.post("/start")
async def start_experiment_request(
start_data: StartExperimentRequest,
db: AsyncSession = Depends(get_db),
payload: dict = Depends(get_current_token_payload),
):
"""Start an experiment (create simulation result for an instance)"""
keycloak_id = payload.get("sub")
if not keycloak_id:
raise HTTPException(403, "Permission denied")
# Get instance to find its experiment
experiment = await get_single_experiment(db, start_data.experiment_id)
if not experiment:
raise HTTPException(404, "Experiment not found")
if not experiment.instances:
raise HTTPException(400, "No instances")
for task in experiment.instances:
if task.simulation_result:
# If simulation result exists, check its status
current_status = task.simulation_result.status.name
if current_status != "DRAFT":
raise HTTPException(
400,
f"Cannot start experiment. Task {task.id} has status '{current_status}'. "
f"Only tasks with 'draft' status can be started.",
)
# Check if user has permission to run experiments in this team
await check_team_permission(
["run_experiments"], experiment.team_id, db, keycloak_id
)
# TODO: add the starting rabbitMQ logic
max_qubits = max(i.qubits_given for i in experiment.team.team_systems)
for task in experiment.instances:
if task.qubits_needed > max_qubits or task.qubits_needed <= 0:
raise HTTPException(
400,
f"Cannot start experiment. Instance {task.id} requires {task.qubits_needed} qubits, "
f"but the maximum available in team's systems is {max_qubits} qubits.",
)
await bind_queues_to_team_exchange(experiment.team_id, max_qubits)
for task in sorted(
experiment.instances, key=lambda x: x.qubits_needed, reverse=True
):
try:
# Publish task to queue
await publish_task_to_team(task)
# Create simulation result with IN_QUEUE status
# Pass None for comp_system_id since it's not assigned yet
simulation_result = await set_simulation_result(
db=db,
comp_system_id=None, # No system assigned yet
instance_id=task.id,
status_name="IN QUEUE",
started_at=datetime.now(),
ended_at=None,
simulation_result_data={},
)
except Exception as e:
# Create failed simulation result
await set_simulation_result(
db=db,
comp_system_id=None,
instance_id=task.id,
status_name="ERROR",
started_at=datetime.now(),
ended_at=datetime.now(),
simulation_result_data={"error": str(e)},
)
raise HTTPException(500, f"Failed to publish task {task.id}: {str(e)}")
return HTTP_200_OK