feat(annotation): 添加标注项目信息更新功能

- 引入 DatasetMappingUpdateRequest 请求模型支持 name、description、template_id 和 label_config 字段更新
- 在项目接口中添加 PUT /{project_id} 端点用于更新标注项目信息
- 实现更新逻辑包括映射记录查询、配置信息处理和数据库更新操作
- 集成标准响应格式返回更新结果
- 添加异常处理和日志记录确保操作可追溯性
This commit is contained in:
2026-01-31 18:54:05 +08:00
parent 4f99875670
commit e28f680abb
2 changed files with 112 additions and 4 deletions

View File

@@ -3,7 +3,7 @@ import math
import uuid import uuid
from fastapi import APIRouter, Depends, HTTPException, Query, Path from fastapi import APIRouter, Depends, HTTPException, Query, Path
from sqlalchemy import select from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import get_db from app.db.session import get_db
@@ -17,6 +17,7 @@ from ..service.template import AnnotationTemplateService
from ..schema import ( from ..schema import (
DatasetMappingCreateRequest, DatasetMappingCreateRequest,
DatasetMappingCreateResponse, DatasetMappingCreateResponse,
DatasetMappingUpdateRequest,
DeleteDatasetResponse, DeleteDatasetResponse,
DatasetMappingResponse, DatasetMappingResponse,
) )
@@ -382,3 +383,97 @@ async def delete_mapping(
except Exception as e: except Exception as e:
logger.error(f"Error deleting mapping: {e}") logger.error(f"Error deleting mapping: {e}")
raise HTTPException(status_code=500, detail="Internal server error") raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/{project_id}", response_model=StandardResponse[DatasetMappingResponse])
async def update_mapping(
project_id: str = Path(..., description="映射UUID(path param)"),
request: DatasetMappingUpdateRequest = None,
db: AsyncSession = Depends(get_db)
):
"""
更新标注项目信息
通过 path 参数 `project_id` 指定要更新的映射(映射的 UUID)。
支持更新的字段:
- name: 标注项目名称
- description: 标注项目描述
- template_id: 标注模板ID
- label_config: Label Studio XML配置
"""
try:
logger.info(f"Update mapping request received: project_id={project_id!r}")
service = DatasetMappingService(db)
# 使用 mapping UUID 查询映射记录
mapping = await service.get_mapping_by_uuid(project_id)
if not mapping:
raise HTTPException(
status_code=404,
detail=f"Mapping not found: {project_id}"
)
# 构建更新数据
update_values = {}
if request.name is not None:
update_values["name"] = request.name
# 从 configuration 字段中读取和更新 description 和 label_config
configuration = {}
if mapping.configuration:
configuration = mapping.configuration.copy() if isinstance(mapping.configuration, dict) else {}
if request.description is not None:
configuration["description"] = request.description
if request.label_config is not None:
configuration["label_config"] = request.label_config
if configuration:
update_values["configuration"] = configuration
if request.template_id is not None:
update_values["template_id"] = request.template_id
if not update_values:
# 没有要更新的字段,直接返回当前数据
return StandardResponse(
code=200,
message="success",
data=mapping
)
# 执行更新
from datetime import datetime
update_values["updated_at"] = datetime.now()
result = await db.execute(
update(LabelingProject)
.where(LabelingProject.id == project_id)
.values(**update_values)
)
await db.commit()
if result.rowcount == 0:
raise HTTPException(
status_code=500,
detail="Failed to update mapping"
)
# 重新获取更新后的数据
updated_mapping = await service.get_mapping_by_uuid(project_id)
logger.info(f"Successfully updated mapping: {project_id}")
return StandardResponse(
code=200,
message="success",
data=updated_mapping
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error updating mapping: {e}")
raise HTTPException(status_code=500, detail="Internal server error")

View File

@@ -39,9 +39,22 @@ class DatasetMappingCreateResponse(BaseResponseModel):
labeling_project_id: str = Field(..., description="Label Studio项目ID") labeling_project_id: str = Field(..., description="Label Studio项目ID")
labeling_project_name: str = Field(..., description="Label Studio项目名称") labeling_project_name: str = Field(..., description="Label Studio项目名称")
class DatasetMappingUpdateRequest(BaseResponseModel): class DatasetMappingUpdateRequest(BaseModel):
"""数据集映射 更新 请求模型""" """数据集映射 更新 请求模型
dataset_id: Optional[str] = Field(None, description="源数据集ID")
支持更新的字段:
- name: 标注项目名称
- description: 标注项目描述
- template_id: 标注模板ID
- label_config: Label Studio XML配置
"""
name: Optional[str] = Field(None, alias="name", description="标注项目名称")
description: Optional[str] = Field(None, alias="description", description="标注项目描述")
template_id: Optional[str] = Field(None, alias="templateId", description="标注模板ID")
label_config: Optional[str] = Field(None, alias="labelConfig", description="Label Studio XML配置")
class Config:
validate_by_name = True
class DatasetMappingResponse(BaseModel): class DatasetMappingResponse(BaseModel):
"""数据集映射 查询 响应模型""" """数据集映射 查询 响应模型"""