feat(phase2): 给 /edit-plans/* 所有API加deprecated标记和warning日志 #640

Merged
auto-approve-bot merged 1 commits from feat/phase2-deprecated-markers into develop 2026-07-20 13:55:17 +08:00
12 changed files with 126 additions and 50 deletions
+23
View File
@@ -139,3 +139,26 @@ def format_utc_datetime(dt: datetime | None) -> str:
if dt.tzinfo is None:
return dt.isoformat() + "Z"
return dt.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
# ── Deprecated API 标记 ──────────────────────────────────────────────────────
import logging as _logging
from fastapi import Request as _Request
_deprecated_logger = _logging.getLogger(__name__)
def deprecated_edit_plans_api(request: _Request) -> None:
"""标记 /edit-plans/* 系列 API 为废弃,打 warning 日志。
Phase 2 模板编辑器收敛后,所有剪辑计划 API 迁移到 /templates/{id}/editor/*。
旧路径保留 2 个版本周期兼容,之后会下线。
"""
_deprecated_logger.warning(
"Deprecated API called: %s %s. Use /templates/{template_id}/editor/* instead.",
request.method,
request.url.path,
)
+21 -8
View File
@@ -38,11 +38,13 @@ from sqlalchemy.orm import Session
from packages.domain.config_schemas import normalize_plan_config
from packages.domain.edit_plan import EditPlan, EditPlanStatus
from ._helpers import auto_select_video_assets, check_project_access
from ._helpers import auto_select_video_assets, check_project_access, deprecated_edit_plans_api
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter(
dependencies=[Depends(deprecated_edit_plans_api)],
)
# ── Pydantic Schemas ─────────────────────────────────────────────────────────
@@ -294,7 +296,7 @@ router.include_router(transitions_router)
# ── CRUD Routes ───────────────────────────────────────────────────────────────
@router.get("", response_model=EditPlanListResponse)
@router.get("", response_model=EditPlanListResponse, deprecated=True)
def list_plans(
page: int = Query(default=1, ge=1, description="页码"),
page_size: int = Query(default=20, ge=1, le=100, description="每页数量"),
@@ -353,7 +355,7 @@ def list_plans(
)
@router.get("/{plan_id}", response_model=EditPlanResponse)
@router.get("/{plan_id}", response_model=EditPlanResponse, deprecated=True)
def get_plan(
plan_id: str,
db: Session = Depends(get_db_session),
@@ -375,7 +377,7 @@ def get_plan(
return _to_response(plan)
@router.post("", response_model=EditPlanResponse, status_code=status.HTTP_201_CREATED)
@router.post("", response_model=EditPlanResponse, status_code=status.HTTP_201_CREATED, deprecated=True)
def create_plan(
body: EditPlanCreateRequest,
db: Session = Depends(get_db_session),
@@ -498,7 +500,7 @@ def create_plan(
return _to_response(plan)
@router.put("/{plan_id}", response_model=EditPlanResponse)
@router.put("/{plan_id}", response_model=EditPlanResponse, deprecated=True)
def update_plan(
plan_id: str,
body: EditPlanUpdateRequest,
@@ -555,7 +557,9 @@ def update_plan(
return _to_response(result)
@router.delete("/{plan_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response)
@router.delete(
"/{plan_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response, deprecated=True
)
def delete_plan(
plan_id: str,
db: Session = Depends(get_db_session),
@@ -581,7 +585,7 @@ def delete_plan(
)
@router.post("/{plan_id}/copy", response_model=EditPlanResponse, status_code=status.HTTP_201_CREATED)
@router.post("/{plan_id}/copy", response_model=EditPlanResponse, status_code=status.HTTP_201_CREATED, deprecated=True)
def copy_plan(
plan_id: str,
body: CopyPlanRequest,
@@ -659,6 +663,7 @@ class SubtitleBatchUpdateRequest(BaseModel):
"/clips/{clip_id}/subtitles",
response_model=list[dict[str, Any]],
summary="获取片段的所有字幕",
deprecated=True,
)
def list_subtitles(
clip_id: str,
@@ -687,6 +692,7 @@ def list_subtitles(
response_model=dict[str, Any],
summary="添加一条字幕",
status_code=status.HTTP_201_CREATED,
deprecated=True,
)
def add_subtitle(
clip_id: str,
@@ -730,6 +736,7 @@ def add_subtitle(
"/clips/{clip_id}/subtitles/{subtitle_id}",
response_model=dict[str, Any],
summary="更新一条字幕",
deprecated=True,
)
def update_subtitle(
clip_id: str,
@@ -782,6 +789,7 @@ def update_subtitle(
status_code=status.HTTP_204_NO_CONTENT,
response_model=None,
response_class=Response,
deprecated=True,
)
def delete_subtitle(
clip_id: str,
@@ -822,6 +830,7 @@ def delete_subtitle(
"/clips/{clip_id}/subtitles",
response_model=list[dict[str, Any]],
summary="批量更新字幕(全量替换)",
deprecated=True,
)
def batch_update_subtitles(
clip_id: str,
@@ -887,6 +896,7 @@ class BGMConfigUpdateRequest(BaseModel):
"/{plan_id}/bgm",
response_model=dict[str, Any],
summary="获取剪辑计划的 BGM 配置",
deprecated=True,
)
def get_plan_bgm(
plan_id: str,
@@ -919,6 +929,7 @@ def get_plan_bgm(
"/{plan_id}/bgm",
response_model=dict[str, Any],
summary="更新剪辑计划的 BGM 配置",
deprecated=True,
)
def update_plan_bgm(
plan_id: str,
@@ -983,6 +994,7 @@ def update_plan_bgm(
"/bgm/presets",
response_model=dict[str, Any],
summary="获取预设 BGM 列表",
deprecated=True,
)
def list_bgm_presets(
style: Optional[str] = Query(default=None, description="按风格筛选"),
@@ -1051,6 +1063,7 @@ class SaveAsTemplateRequest(BaseModel):
response_model=dict[str, Any],
summary="将剪辑计划保存为模板",
status_code=status.HTTP_201_CREATED,
deprecated=True,
)
def save_plan_as_template(
plan_id: str,
+9 -7
View File
@@ -19,11 +19,13 @@ from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from ._helpers import check_project_access
from ._helpers import check_project_access, deprecated_edit_plans_api
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter(
dependencies=[Depends(deprecated_edit_plans_api)],
)
# ── Schemas ──────────────────────────────────────────────────────────────────
@@ -138,7 +140,7 @@ def _get_svc_and_plan_and_clip(db, clip_id, current_user, project_repository):
return svc, plan, clip
@router.put("/clips/{clip_id}/speed", response_model=ClipAdjustResponse)
@router.put("/clips/{clip_id}/speed", response_model=ClipAdjustResponse, deprecated=True)
def adjust_speed(
clip_id: str,
body: SpeedAdjustRequest,
@@ -160,7 +162,7 @@ def adjust_speed(
return _build_response(updated)
@router.put("/clips/{clip_id}/volume", response_model=ClipAdjustResponse)
@router.put("/clips/{clip_id}/volume", response_model=ClipAdjustResponse, deprecated=True)
def adjust_volume(
clip_id: str,
body: VolumeAdjustRequest,
@@ -185,7 +187,7 @@ def adjust_volume(
return _build_response(updated)
@router.put("/clips/{clip_id}/trim", response_model=ClipAdjustResponse)
@router.put("/clips/{clip_id}/trim", response_model=ClipAdjustResponse, deprecated=True)
def adjust_trim(
clip_id: str,
body: TrimAdjustRequest,
@@ -218,7 +220,7 @@ def adjust_trim(
return _build_response(updated)
@router.put("/clips/{clip_id}/adjustments", response_model=ClipAdjustResponse)
@router.put("/clips/{clip_id}/adjustments", response_model=ClipAdjustResponse, deprecated=True)
def adjust_all(
clip_id: str,
body: ClipAdjustmentsRequest,
@@ -275,7 +277,7 @@ def adjust_all(
return _build_response(updated)
@router.post("/{plan_id}/clips/batch-speed", response_model=BatchSpeedResponse)
@router.post("/{plan_id}/clips/batch-speed", response_model=BatchSpeedResponse, deprecated=True)
def batch_adjust_speed(
plan_id: str,
body: BatchSpeedRequest,
+7 -1
View File
@@ -26,14 +26,19 @@ from sqlalchemy.orm import Session
from packages.domain.config_schemas import normalize_plan_config
from ._helpers import deprecated_edit_plans_api
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter(
dependencies=[Depends(deprecated_edit_plans_api)],
)
@router.post(
"/{plan_id}/ai-recommend",
response_model=AIRecommendResponse,
deprecated=True,
)
def ai_recommend_clips(
plan_id: str,
@@ -150,6 +155,7 @@ def ai_recommend_clips(
@router.post(
"/{plan_id}/generate-cover",
response_model=GenerateCoverResponse,
deprecated=True,
)
def generate_cover(
plan_id: str,
+14 -6
View File
@@ -12,9 +12,13 @@ from sqlalchemy.orm import Session
from packages.domain.edit_plan_clip import EditPlanClipStatus
from ._helpers import deprecated_edit_plans_api
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter(
dependencies=[Depends(deprecated_edit_plans_api)],
)
# ── Schemas ──────────────────────────────────────────────────────────────────
@@ -132,7 +136,7 @@ def _get_svc(db: Session):
# ── Routes ───────────────────────────────────────────────────────────────────
@router.get("", response_model=EditPlanClipListResponse)
@router.get("", response_model=EditPlanClipListResponse, deprecated=True)
def list_clips(
plan_id: str,
status_filter: Optional[str] = Query(None, alias="status", description="按状态过滤"),
@@ -156,7 +160,7 @@ def list_clips(
)
@router.post("", response_model=EditPlanClipResponse, status_code=status.HTTP_201_CREATED)
@router.post("", response_model=EditPlanClipResponse, status_code=status.HTTP_201_CREATED, deprecated=True)
def create_clip(
plan_id: str,
body: EditPlanClipCreateRequest,
@@ -189,7 +193,7 @@ def create_clip(
return _clip_to_response(clip)
@router.get("/{clip_id}", response_model=EditPlanClipResponse)
@router.get("/{clip_id}", response_model=EditPlanClipResponse, deprecated=True)
def get_clip(
plan_id: str,
clip_id: str,
@@ -210,7 +214,7 @@ def get_clip(
return _clip_to_response(clip)
@router.put("/{clip_id}", response_model=EditPlanClipResponse)
@router.put("/{clip_id}", response_model=EditPlanClipResponse, deprecated=True)
def update_clip(
plan_id: str,
clip_id: str,
@@ -251,7 +255,9 @@ def update_clip(
return _clip_to_response(updated)
@router.delete("/{clip_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response)
@router.delete(
"/{clip_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response, deprecated=True
)
def delete_clip(
plan_id: str,
clip_id: str,
@@ -298,6 +304,7 @@ class MergeClipsRequest(BaseModel):
response_model=dict[str, Any],
summary="分割片段",
status_code=status.HTTP_200_OK,
deprecated=True,
)
def split_clip(
plan_id: str,
@@ -359,6 +366,7 @@ def split_clip(
response_model=dict[str, Any],
summary="合并多个连续片段",
status_code=status.HTTP_200_OK,
deprecated=True,
)
def merge_clips(
plan_id: str,
@@ -11,9 +11,13 @@ from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from ._helpers import deprecated_edit_plans_api
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter(
dependencies=[Depends(deprecated_edit_plans_api)],
)
# ── Schemas ──────────────────────────────────────────────────────────────────
@@ -98,7 +102,7 @@ def _get_svc(db: Session):
# ── Routes ───────────────────────────────────────────────────────────────────
@router.post("/reorder", response_model=ClipReorderResponse)
@router.post("/reorder", response_model=ClipReorderResponse, deprecated=True)
def reorder_clips(
plan_id: str,
body: ClipReorderRequest,
@@ -150,7 +154,7 @@ def reorder_clips(
)
@router.post("/batch-delete", response_model=ClipBatchDeleteResponse)
@router.post("/batch-delete", response_model=ClipBatchDeleteResponse, deprecated=True)
def batch_delete_clips(
plan_id: str,
body: ClipBatchDeleteRequest,
@@ -198,7 +202,7 @@ def batch_delete_clips(
)
@router.post("/from-assets", response_model=ClipsFromAssetsResponse)
@router.post("/from-assets", response_model=ClipsFromAssetsResponse, deprecated=True)
def create_clips_from_assets(
plan_id: str,
body: ClipsFromAssetsRequest,
+8 -6
View File
@@ -26,11 +26,13 @@ from sqlalchemy.orm import Session
from packages.domain.config_schemas import normalize_plan_config
from ._helpers import check_project_access
from ._helpers import check_project_access, deprecated_edit_plans_api
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter(
dependencies=[Depends(deprecated_edit_plans_api)],
)
# ── Schemas ──────────────────────────────────────────────────────────────────
@@ -76,7 +78,7 @@ class CoverGenerateResponse(BaseModel):
# ── Routes ───────────────────────────────────────────────────────────────────
@router.get("/{plan_id}/cover", response_model=CoverConfigResponse)
@router.get("/{plan_id}/cover", response_model=CoverConfigResponse, deprecated=True)
def get_cover(
plan_id: str,
db: Session = Depends(get_db_session),
@@ -99,7 +101,7 @@ def get_cover(
return CoverConfigResponse(**cover)
@router.put("/{plan_id}/cover", response_model=CoverConfigResponse)
@router.put("/{plan_id}/cover", response_model=CoverConfigResponse, deprecated=True)
def update_cover(
plan_id: str,
body: CoverUpdateRequest,
@@ -146,7 +148,7 @@ def update_cover(
return CoverConfigResponse(**result)
@router.post("/{plan_id}/cover/extract", response_model=CoverGenerateResponse)
@router.post("/{plan_id}/cover/extract", response_model=CoverGenerateResponse, deprecated=True)
def extract_cover(
plan_id: str,
body: CoverExtractRequest,
@@ -221,7 +223,7 @@ def extract_cover(
return CoverGenerateResponse(**cover_data)
@router.post("/{plan_id}/cover/smart", response_model=CoverGenerateResponse)
@router.post("/{plan_id}/cover/smart", response_model=CoverGenerateResponse, deprecated=True)
def smart_cover(
plan_id: str,
body: CoverSmartRequest,
+7 -5
View File
@@ -20,11 +20,13 @@ from sqlalchemy.orm import Session
from packages.domain.config_schemas import normalize_plan_config
from ._helpers import check_project_access
from ._helpers import check_project_access, deprecated_edit_plans_api
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter(
dependencies=[Depends(deprecated_edit_plans_api)],
)
# ── 导出预设 ──────────────────────────────────────────────────────────────────
@@ -200,7 +202,7 @@ def _get_export_config(plan_config: dict) -> dict:
# ── Routes ───────────────────────────────────────────────────────────────────
@router.get("/export-presets", response_model=ExportPresetListResponse)
@router.get("/export-presets", response_model=ExportPresetListResponse, deprecated=True)
def list_export_presets(
current_user: AuthenticatedUser = Depends(get_current_user),
) -> ExportPresetListResponse:
@@ -209,7 +211,7 @@ def list_export_presets(
return ExportPresetListResponse(items=items, total=len(items))
@router.get("/{plan_id}/export", response_model=ExportConfigResponse)
@router.get("/{plan_id}/export", response_model=ExportConfigResponse, deprecated=True)
def get_export_config(
plan_id: str,
db: Session = Depends(get_db_session),
@@ -232,7 +234,7 @@ def get_export_config(
return ExportConfigResponse(**config)
@router.put("/{plan_id}/export", response_model=ExportConfigResponse)
@router.put("/{plan_id}/export", response_model=ExportConfigResponse, deprecated=True)
def update_export_config(
plan_id: str,
body: ExportUpdateRequest,
+7 -5
View File
@@ -24,11 +24,13 @@ from packages.domain.filter_presets import (
list_filter_presets,
)
from ._helpers import check_project_access
from ._helpers import check_project_access, deprecated_edit_plans_api
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter(
dependencies=[Depends(deprecated_edit_plans_api)],
)
# ── Schemas ──────────────────────────────────────────────────────────────────
@@ -107,7 +109,7 @@ def _get_filter_config(plan_config: dict) -> dict:
# ── Routes ───────────────────────────────────────────────────────────────────
@router.get("/filter-presets", response_model=FilterPresetListResponse)
@router.get("/filter-presets", response_model=FilterPresetListResponse, deprecated=True)
def list_presets(
category: Optional[str] = Query(default=None, description="按分类筛选"),
keyword: Optional[str] = Query(default=None, description="关键词搜索"),
@@ -119,7 +121,7 @@ def list_presets(
return FilterPresetListResponse(items=items, total=len(items))
@router.get("/{plan_id}/filter", response_model=FilterConfigResponse)
@router.get("/{plan_id}/filter", response_model=FilterConfigResponse, deprecated=True)
def get_filter(
plan_id: str,
db: Session = Depends(get_db_session),
@@ -142,7 +144,7 @@ def get_filter(
return FilterConfigResponse(**config)
@router.put("/{plan_id}/filter", response_model=FilterConfigResponse)
@router.put("/{plan_id}/filter", response_model=FilterConfigResponse, deprecated=True)
def update_filter(
plan_id: str,
body: FilterUpdateRequest,
+8 -2
View File
@@ -42,9 +42,13 @@ from packages.application.generation_tasks import (
)
from packages.domain.edit_plan import EditPlanStatus
from ._helpers import deprecated_edit_plans_api
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter(
dependencies=[Depends(deprecated_edit_plans_api)],
)
def _auto_fallback_draft_to_editing(svc: EditPlanService, plan_id: str, plan_check) -> None:
@@ -208,7 +212,7 @@ def _check_queue_limits(gen_task_repo, user_id: str) -> None:
logger.warning("[队列限流] 剪辑计划限流检查失败,跳过: %s", e)
@router.post("/{plan_id}/generate", response_model=EditPlanGenerateResponse)
@router.post("/{plan_id}/generate", response_model=EditPlanGenerateResponse, deprecated=True)
def generate_plan(
plan_id: str,
db: Session = Depends(get_db_session),
@@ -307,6 +311,7 @@ def generate_plan(
@router.get(
"/{plan_id}/generation-status",
response_model=EditPlanGenerationStatusResponse,
deprecated=True,
)
def get_generation_status(
plan_id: str,
@@ -373,6 +378,7 @@ def get_generation_status(
@router.get(
"/{plan_id}/generations",
response_model=EditPlanGenerationsResponse,
deprecated=True,
)
def list_plan_generations(
plan_id: str,
@@ -29,9 +29,13 @@ from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from ._helpers import deprecated_edit_plans_api
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter(
dependencies=[Depends(deprecated_edit_plans_api)],
)
# ── Timeline Schemas ──────────────────────────────────────────────────────────
@@ -110,6 +114,7 @@ def _clip_type_to_scene_label(clip_type: str, text_content: str) -> str:
@router.get(
"/{plan_id}/timeline",
response_model=TimelineResponse,
deprecated=True,
)
def get_plan_timeline(
plan_id: str,
@@ -160,6 +165,7 @@ def get_plan_timeline(
"/generate-from-template",
response_model=GenerateFromTemplateResponse,
status_code=status.HTTP_201_CREATED,
deprecated=True,
)
def generate_from_template(
body: GenerateFromTemplateRequest,
+7 -5
View File
@@ -23,11 +23,13 @@ from packages.domain.transition_presets import (
list_transition_presets,
)
from ._helpers import check_project_access
from ._helpers import check_project_access, deprecated_edit_plans_api
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter(
dependencies=[Depends(deprecated_edit_plans_api)],
)
# ── Schemas ──────────────────────────────────────────────────────────────────
@@ -124,7 +126,7 @@ def _validate_transition(effect: str, duration: Optional[float] = None) -> tuple
# ── Routes ───────────────────────────────────────────────────────────────────
@router.get("/transition-presets", response_model=TransitionPresetListResponse)
@router.get("/transition-presets", response_model=TransitionPresetListResponse, deprecated=True)
def list_presets(
category: Optional[str] = Query(default=None, description="按分类筛选"),
keyword: Optional[str] = Query(default=None, description="关键词搜索"),
@@ -136,7 +138,7 @@ def list_presets(
return TransitionPresetListResponse(items=items, total=len(items))
@router.put("/clips/{clip_id}/transition", response_model=ClipTransitionResponse)
@router.put("/clips/{clip_id}/transition", response_model=ClipTransitionResponse, deprecated=True)
def update_clip_transition(
clip_id: str,
body: TransitionUpdateRequest,
@@ -184,7 +186,7 @@ def update_clip_transition(
)
@router.post("/{plan_id}/transitions/batch", response_model=BatchTransitionResponse)
@router.post("/{plan_id}/transitions/batch", response_model=BatchTransitionResponse, deprecated=True)
def batch_update_transitions(
plan_id: str,
body: BatchTransitionRequest,