From ee7e4aca37acf6e466e97e4062bbff8904476090 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 30 Oct 2025 16:06:03 +0800 Subject: [PATCH 1/2] feature: implement endpoints with multi-level response models --- .../datamate-python/app/module/__init__.py | 2 + .../module/annotation/interface/project.py | 6 +-- .../app/module/annotation/schema/__init__.py | 10 ++--- .../app/module/annotation/schema/mapping.py | 11 +++-- .../app/module/annotation/schema/sync.py | 6 ++- .../app/module/management/api/__init__.py | 0 .../app/module/management/api/system.py | 33 --------------- .../app/module/management/service/__init__.py | 0 .../app/module/shared/schema/common.py | 41 ++++++++++--------- .../module/{management => system}/__init__.py | 0 .../app/module/system/interface/__init__.py | 7 ++++ .../app/module/system/interface/about.py | 36 ++++++++++++++++ .../app/module/system/schema/__init__.py | 4 ++ .../app/module/system/schema/config.py | 11 +++++ .../app/module/system/schema/health.py | 10 +++++ 15 files changed, 107 insertions(+), 70 deletions(-) delete mode 100644 runtime/datamate-python/app/module/management/api/__init__.py delete mode 100644 runtime/datamate-python/app/module/management/api/system.py delete mode 100644 runtime/datamate-python/app/module/management/service/__init__.py rename runtime/datamate-python/app/module/{management => system}/__init__.py (100%) create mode 100644 runtime/datamate-python/app/module/system/interface/__init__.py create mode 100644 runtime/datamate-python/app/module/system/interface/about.py create mode 100644 runtime/datamate-python/app/module/system/schema/__init__.py create mode 100644 runtime/datamate-python/app/module/system/schema/config.py create mode 100644 runtime/datamate-python/app/module/system/schema/health.py diff --git a/runtime/datamate-python/app/module/__init__.py b/runtime/datamate-python/app/module/__init__.py index 4ca37828a..a8b7d7994 100644 --- a/runtime/datamate-python/app/module/__init__.py +++ b/runtime/datamate-python/app/module/__init__.py @@ -1,11 +1,13 @@ from fastapi import APIRouter +from .system.interface import router as system_router from .annotation.interface import router as annotation_router router = APIRouter( prefix="/api" ) +router.include_router(system_router) router.include_router(annotation_router) __all__ = ["router"] \ No newline at end of file diff --git a/runtime/datamate-python/app/module/annotation/interface/project.py b/runtime/datamate-python/app/module/annotation/interface/project.py index c22a3d927..5896e41f0 100644 --- a/runtime/datamate-python/app/module/annotation/interface/project.py +++ b/runtime/datamate-python/app/module/annotation/interface/project.py @@ -108,8 +108,7 @@ async def create_mapping( response_data = DatasetMappingCreateResponse( id=mapping.id, labeling_project_id=str(mapping.labeling_project_id), - labeling_project_name=mapping.name or project_name, - message="Dataset mapping created successfully" + labeling_project_name=mapping.name or project_name ) return StandardResponse( @@ -341,8 +340,7 @@ async def delete_mapping( message="success", data=DeleteDatasetResponse( id=id, - status="success", - message=f"Successfully deleted mapping and Label Studio project '{labeling_project_name}'" + status="success" ) ) diff --git a/runtime/datamate-python/app/module/annotation/schema/__init__.py b/runtime/datamate-python/app/module/annotation/schema/__init__.py index 289a327a8..bcbd6e913 100644 --- a/runtime/datamate-python/app/module/annotation/schema/__init__.py +++ b/runtime/datamate-python/app/module/annotation/schema/__init__.py @@ -1,24 +1,24 @@ from .mapping import ( - DatasetMappingBase, + _DatasetMappingBase, DatasetMappingCreateRequest, DatasetMappingCreateResponse, DatasetMappingUpdateRequest, DatasetMappingResponse, - DeleteDatasetResponse + DeleteDatasetResponse, ) from .sync import ( SyncDatasetRequest, - SyncDatasetResponse + SyncDatasetResponse, ) __all__ = [ - "DatasetMappingBase", + "_DatasetMappingBase", "DatasetMappingCreateRequest", "DatasetMappingCreateResponse", "DatasetMappingUpdateRequest", "DatasetMappingResponse", "SyncDatasetRequest", "SyncDatasetResponse", - "DeleteDatasetResponse" + "DeleteDatasetResponse", ] \ No newline at end of file diff --git a/runtime/datamate-python/app/module/annotation/schema/mapping.py b/runtime/datamate-python/app/module/annotation/schema/mapping.py index d0c80d889..212059b22 100644 --- a/runtime/datamate-python/app/module/annotation/schema/mapping.py +++ b/runtime/datamate-python/app/module/annotation/schema/mapping.py @@ -3,12 +3,13 @@ from datetime import datetime from app.module.shared.schema import BaseResponseModel +from app.module.shared.schema import StandardResponse -class DatasetMappingBase(BaseResponseModel): +class _DatasetMappingBase(BaseResponseModel): """数据集映射 基础模型""" dataset_id: str = Field(..., description="源数据集ID") -class DatasetMappingCreateRequest(DatasetMappingBase): +class DatasetMappingCreateRequest(_DatasetMappingBase): """数据集映射 创建 请求模型""" pass @@ -17,13 +18,12 @@ class DatasetMappingCreateResponse(BaseResponseModel): id: str = Field(..., description="映射UUID") labeling_project_id: str = Field(..., description="Label Studio项目ID") labeling_project_name: str = Field(..., description="Label Studio项目名称") - message: str = Field(..., description="响应消息") class DatasetMappingUpdateRequest(BaseResponseModel): """数据集映射 更新 请求模型""" dataset_id: Optional[str] = Field(None, description="源数据集ID") -class DatasetMappingResponse(DatasetMappingBase): +class DatasetMappingResponse(_DatasetMappingBase): """数据集映射 查询 响应模型""" id: str = Field(..., description="映射UUID") labeling_project_id: str = Field(..., description="标注项目ID") @@ -38,5 +38,4 @@ class Config: class DeleteDatasetResponse(BaseResponseModel): """删除数据集响应模型""" id: str = Field(..., description="映射UUID") - status: str = Field(..., description="删除状态") - message: str = Field(..., description="响应消息") \ No newline at end of file + status: str = Field(..., description="删除状态") \ No newline at end of file diff --git a/runtime/datamate-python/app/module/annotation/schema/sync.py b/runtime/datamate-python/app/module/annotation/schema/sync.py index 492d372ae..6604c8ccf 100644 --- a/runtime/datamate-python/app/module/annotation/schema/sync.py +++ b/runtime/datamate-python/app/module/annotation/schema/sync.py @@ -1,8 +1,7 @@ from pydantic import Field -from typing import Optional -from datetime import datetime from app.module.shared.schema import BaseResponseModel +from app.module.shared.schema import StandardResponse class SyncDatasetRequest(BaseResponseModel): @@ -17,3 +16,6 @@ class SyncDatasetResponse(BaseResponseModel): synced_files: int = Field(..., description="已同步文件数量") total_files: int = Field(0, description="总文件数量") message: str = Field(..., description="响应消息") + +class SyncDatasetResponseStd(StandardResponse[SyncDatasetResponse]): + pass \ No newline at end of file diff --git a/runtime/datamate-python/app/module/management/api/__init__.py b/runtime/datamate-python/app/module/management/api/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/runtime/datamate-python/app/module/management/api/system.py b/runtime/datamate-python/app/module/management/api/system.py deleted file mode 100644 index aa38d8718..000000000 --- a/runtime/datamate-python/app/module/management/api/system.py +++ /dev/null @@ -1,33 +0,0 @@ -from fastapi import APIRouter -from typing import Dict, Any -from app.core.config import settings -from app.schemas import StandardResponse - -router = APIRouter() - -@router.get("/health", response_model=StandardResponse[Dict[str, Any]]) -async def health_check(): - """健康检查端点""" - return StandardResponse( - code=200, - message="success", - data={ - "status": "healthy", - "service": "Label Studio Adapter", - "version": settings.app_version - } - ) - -@router.get("/config", response_model=StandardResponse[Dict[str, Any]]) -async def get_config(): - """获取配置信息""" - return StandardResponse( - code=200, - message="success", - data={ - "app_name": settings.app_name, - "version": settings.app_version, - "label_studio_url": settings.label_studio_base_url, - "debug": settings.debug - } - ) \ No newline at end of file diff --git a/runtime/datamate-python/app/module/management/service/__init__.py b/runtime/datamate-python/app/module/management/service/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/runtime/datamate-python/app/module/shared/schema/common.py b/runtime/datamate-python/app/module/shared/schema/common.py index b0681d12b..db55a4a14 100644 --- a/runtime/datamate-python/app/module/shared/schema/common.py +++ b/runtime/datamate-python/app/module/shared/schema/common.py @@ -1,7 +1,7 @@ """ 通用响应模型 """ -from typing import Generic, TypeVar, Optional, List +from typing import Generic, TypeVar, Optional, List, Type from pydantic import BaseModel, Field # 定义泛型类型变量 @@ -29,18 +29,11 @@ class StandardResponse(BaseResponseModel, Generic[T]): """ code: int = Field(..., description="HTTP状态码") message: str = Field(..., description="响应消息") - data: Optional[T] = Field(None, description="响应数据") - + data: T = Field(..., description="响应数据") + class Config: populate_by_name = True alias_generator = to_camel - json_schema_extra = { - "example": { - "code": 200, - "message": "success", - "data": {} - } - } class PaginatedData(BaseResponseModel, Generic[T]): """分页数据容器""" @@ -49,14 +42,22 @@ class PaginatedData(BaseResponseModel, Generic[T]): total_elements: int = Field(..., description="总条数") total_pages: int = Field(..., description="总页数") content: List[T] = Field(..., description="当前页数据") + +T_Data = TypeVar("T_Data") +def get_standard_response_model(data_type: Type[T_Data]) -> Type[StandardResponse[T_Data]]: + """ + 根据给定的数据类型,动态创建并返回一个继承自 StandardResponse[data_type] 的新类。 - class Config: - json_schema_extra = { - "example": { - "page": 1, - "size": 20, - "totalElements": 100, - "totalPages": 5, - "content": [] - } - } + FastAPI/Pydantic 需要一个具体的、非泛型的类作为 response_model。 + """ + # 动态创建类名,确保每个组合都有唯一的名称,防止命名冲突 + response_model_name = f"StandardResponse_{data_type.__name__}" + + # 动态创建一个继承 StandardResponse[data_type] 的新类 + NewStandardResponse = type( + response_model_name, + (StandardResponse[data_type],), + {"__doc__": f"Standard Response wrapper for {data_type.__name__}"} + ) + + return NewStandardResponse \ No newline at end of file diff --git a/runtime/datamate-python/app/module/management/__init__.py b/runtime/datamate-python/app/module/system/__init__.py similarity index 100% rename from runtime/datamate-python/app/module/management/__init__.py rename to runtime/datamate-python/app/module/system/__init__.py diff --git a/runtime/datamate-python/app/module/system/interface/__init__.py b/runtime/datamate-python/app/module/system/interface/__init__.py new file mode 100644 index 000000000..acd102360 --- /dev/null +++ b/runtime/datamate-python/app/module/system/interface/__init__.py @@ -0,0 +1,7 @@ +from fastapi import APIRouter + +from .about import router as about_router + +router = APIRouter() + +router.include_router(about_router) \ No newline at end of file diff --git a/runtime/datamate-python/app/module/system/interface/about.py b/runtime/datamate-python/app/module/system/interface/about.py new file mode 100644 index 000000000..f81b2eff5 --- /dev/null +++ b/runtime/datamate-python/app/module/system/interface/about.py @@ -0,0 +1,36 @@ +from fastapi import APIRouter +from typing import Dict, Any +from app.core.config import settings +from app.module.shared.schema import StandardResponse + +from ..schema import ConfigResponse, HealthResponse + +router = APIRouter() + +@router.get("/health", response_model=StandardResponse[HealthResponse]) +async def health_check(): + """健康检查端点""" + + return StandardResponse( + code=200, + message="success", + data=HealthResponse( + status="healthy", + service="Label Studio Adapter", + version=settings.app_version + ) + ) + +@router.get("/config", response_model=StandardResponse[ConfigResponse]) +async def get_config(): + """获取配置信息""" + return StandardResponse( + code=200, + message="success", + data=ConfigResponse( + app_name=settings.app_name, + version=settings.app_version, + label_studio_url=settings.label_studio_base_url, + debug=settings.debug + ) + ) \ No newline at end of file diff --git a/runtime/datamate-python/app/module/system/schema/__init__.py b/runtime/datamate-python/app/module/system/schema/__init__.py new file mode 100644 index 000000000..c2bcb38c6 --- /dev/null +++ b/runtime/datamate-python/app/module/system/schema/__init__.py @@ -0,0 +1,4 @@ +from .config import ConfigResponse +from .health import HealthResponse + +__all__ = ["ConfigResponse", "HealthResponse"] \ No newline at end of file diff --git a/runtime/datamate-python/app/module/system/schema/config.py b/runtime/datamate-python/app/module/system/schema/config.py new file mode 100644 index 000000000..279c1564c --- /dev/null +++ b/runtime/datamate-python/app/module/system/schema/config.py @@ -0,0 +1,11 @@ +from pydantic import Field + +from app.module.shared.schema import BaseResponseModel +from app.module.shared.schema import StandardResponse + +class ConfigResponse(BaseResponseModel): + """配置信息响应模型""" + app_name: str = Field(..., description="应用名称") + version: str = Field(..., description="应用版本") + label_studio_url: str = Field(..., description="Label Studio基础URL") + debug: bool = Field(..., description="调试模式状态") \ No newline at end of file diff --git a/runtime/datamate-python/app/module/system/schema/health.py b/runtime/datamate-python/app/module/system/schema/health.py new file mode 100644 index 000000000..689ec5081 --- /dev/null +++ b/runtime/datamate-python/app/module/system/schema/health.py @@ -0,0 +1,10 @@ +from pydantic import Field + +from app.module.shared.schema import BaseResponseModel +from app.module.shared.schema import StandardResponse + +class HealthResponse(BaseResponseModel): + """健康检查响应模型""" + status: str = Field(..., description="服务状态") + service: str = Field(..., description="服务名称") + version: str = Field(..., description="应用版本") \ No newline at end of file From 5cf4fcdbfdf449dd59e9abadc66456b783825cab Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 30 Oct 2025 16:21:58 +0800 Subject: [PATCH 2/2] refactor: remove unused get_standard_response_model() --- .../app/module/shared/schema/common.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/runtime/datamate-python/app/module/shared/schema/common.py b/runtime/datamate-python/app/module/shared/schema/common.py index db55a4a14..c79231aa8 100644 --- a/runtime/datamate-python/app/module/shared/schema/common.py +++ b/runtime/datamate-python/app/module/shared/schema/common.py @@ -42,22 +42,3 @@ class PaginatedData(BaseResponseModel, Generic[T]): total_elements: int = Field(..., description="总条数") total_pages: int = Field(..., description="总页数") content: List[T] = Field(..., description="当前页数据") - -T_Data = TypeVar("T_Data") -def get_standard_response_model(data_type: Type[T_Data]) -> Type[StandardResponse[T_Data]]: - """ - 根据给定的数据类型,动态创建并返回一个继承自 StandardResponse[data_type] 的新类。 - - FastAPI/Pydantic 需要一个具体的、非泛型的类作为 response_model。 - """ - # 动态创建类名,确保每个组合都有唯一的名称,防止命名冲突 - response_model_name = f"StandardResponse_{data_type.__name__}" - - # 动态创建一个继承 StandardResponse[data_type] 的新类 - NewStandardResponse = type( - response_model_name, - (StandardResponse[data_type],), - {"__doc__": f"Standard Response wrapper for {data_type.__name__}"} - ) - - return NewStandardResponse \ No newline at end of file