diff --git a/apps/api/app/api/routes/duplication.py b/apps/api/app/api/routes/duplication.py index bdf9094f9..48eb3c92d 100644 --- a/apps/api/app/api/routes/duplication.py +++ b/apps/api/app/api/routes/duplication.py @@ -239,7 +239,9 @@ def get_duplication_detail( return _to_detail_response(record) -@router.delete("/records/{record_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) +@router.delete( + "/records/{record_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response +) def delete_duplication_record( record_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -287,7 +289,7 @@ def retry_duplication( raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), - ) + ) from e if updated is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, diff --git a/apps/api/app/api/routes/edit_plans.py b/apps/api/app/api/routes/edit_plans.py index a830cd193..280b20fba 100644 --- a/apps/api/app/api/routes/edit_plans.py +++ b/apps/api/app/api/routes/edit_plans.py @@ -275,11 +275,11 @@ def list_plans( if status_filter: try: status_enum = EditPlanStatus(status_filter) - except ValueError: + except ValueError as _e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="无效的筛选条件,请选择正确的状态", - ) + ) from _e # 项目鉴权:如果指定了 project_id,校验用户是否有权访问 if project_id: @@ -322,7 +322,7 @@ def get_plan( raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=str(exc), - ) + ) from exc # 项目鉴权 if plan.project_id: check_project_access(plan.project_id, current_user.user.id, project_repository) @@ -358,7 +358,7 @@ def create_plan( raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc), - ) + ) from exc logger.info( "创建剪辑计划: id=%s name=%s by user=%s", created.id, @@ -401,11 +401,11 @@ def update_plan( if body.status is not None: try: target_status = EditPlanStatus(body.status) - except ValueError: + except ValueError as _e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="无效的状态值,请选择正确的状态", - ) + ) from _e svc.transition_status(plan_id, target_status) except ValueError as exc: err_msg = str(exc) @@ -413,11 +413,11 @@ def update_plan( raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=err_msg, - ) + ) from exc raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=err_msg, - ) + ) from exc # 返回最新状态 result = svc.get_plan_or_raise(plan_id) diff --git a/apps/api/app/api/routes/edit_plans_ai.py b/apps/api/app/api/routes/edit_plans_ai.py index 83c5b6bc4..015192b95 100644 --- a/apps/api/app/api/routes/edit_plans_ai.py +++ b/apps/api/app/api/routes/edit_plans_ai.py @@ -58,7 +58,7 @@ def ai_recommend_clips( try: plan = svc.get_plan_or_raise(plan_id) except ValueError as exc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc if plan.project_id: check_project_access(plan.project_id, current_user.user.id, project_repository) @@ -103,7 +103,7 @@ def ai_recommend_clips( config=normalized_config, total_duration=result["total_duration"], ) - except Exception: + except Exception as _e: logger.exception("AI 推荐写入失败,plan_id=%s 数据可能不一致", plan_id) try: db.rollback() @@ -116,7 +116,7 @@ def ai_recommend_clips( raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="AI推荐结果保存失败,请稍后重试", - ) + ) from _e logger.info( "AI 推荐片段方案: plan_id=%s clips=%d duration=%.1f by user=%s", @@ -167,7 +167,7 @@ def generate_cover( try: plan = svc.get_plan_or_raise(plan_id) except ValueError as exc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc if plan.project_id: check_project_access(plan.project_id, current_user.user.id, project_repository) diff --git a/apps/api/app/api/routes/edit_plans_generation.py b/apps/api/app/api/routes/edit_plans_generation.py index 7d7e37039..1fb37412b 100644 --- a/apps/api/app/api/routes/edit_plans_generation.py +++ b/apps/api/app/api/routes/edit_plans_generation.py @@ -183,9 +183,7 @@ def _auto_fallback_auto_material_mode( def _check_queue_limits(gen_task_repo, user_id: str) -> None: """队列限流预检查""" try: - has_count = hasattr(gen_task_repo, "count_pending_by_user") and hasattr( - gen_task_repo, "count_pending_total" - ) + has_count = hasattr(gen_task_repo, "count_pending_by_user") and hasattr(gen_task_repo, "count_pending_total") if has_count: user_pending = gen_task_repo.count_pending_by_user(user_id) global_pending = gen_task_repo.count_pending_total() @@ -241,7 +239,7 @@ def generate_plan( try: can_gen, reason = svc.can_generate(plan_id) except ValueError as exc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc if not can_gen: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=reason) @@ -286,7 +284,7 @@ def generate_plan( ) except HTTPException: raise - except Exception: + except Exception as _e: logger.exception("触发剪辑计划生成失败: plan_id=%s", plan_id) try: svc.transition_status(plan_id, EditPlanStatus.FAILED) @@ -295,7 +293,7 @@ def generate_plan( raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="生成失败,请稍后重试", - ) + ) from _e @router.get( @@ -313,7 +311,7 @@ def get_generation_status( try: gen_status = svc.get_generation_status(plan_id) except ValueError as exc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc plan = gen_status["plan"] if plan.project_id: diff --git a/apps/api/app/api/routes/edit_plans_timeline.py b/apps/api/app/api/routes/edit_plans_timeline.py index c1e6f1249..29c82c7b7 100755 --- a/apps/api/app/api/routes/edit_plans_timeline.py +++ b/apps/api/app/api/routes/edit_plans_timeline.py @@ -173,7 +173,7 @@ def generate_from_template( try: template = template_svc.get_template_or_raise(body.template_id) except ValueError as exc: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc clip_configs = template_svc.list_clip_configs(body.template_id, skip=0, limit=200) diff --git a/apps/api/app/api/routes/feature_flags.py b/apps/api/app/api/routes/feature_flags.py index a3dff32f0..c5834a5a6 100755 --- a/apps/api/app/api/routes/feature_flags.py +++ b/apps/api/app/api/routes/feature_flags.py @@ -105,7 +105,7 @@ async def list_feature_flags( return sorted(result, key=lambda x: x.name) except Exception as exc: logger.error("Failed to list feature flags: %s", exc) - raise HTTPException(status_code=500, detail=f"Failed to list flags: {exc}") + raise HTTPException(status_code=500, detail=f"Failed to list flags: {exc}") from exc @router.get("/{name}", response_model=FeatureFlagResponse) @@ -120,7 +120,7 @@ async def get_feature_flag( return FeatureFlagResponse.from_config(config) except Exception as exc: logger.error("Failed to get feature flag %s: %s", name, exc) - raise HTTPException(status_code=500, detail=f"Failed to get flag: {exc}") + raise HTTPException(status_code=500, detail=f"Failed to get flag: {exc}") from exc @router.get("/{name}/check", response_model=FeatureFlagCheckResponse) @@ -136,7 +136,7 @@ async def check_feature_flag( return FeatureFlagCheckResponse(name=name, active=active, identifier=identifier) except Exception as exc: logger.error("Failed to check feature flag %s: %s", name, exc) - raise HTTPException(status_code=500, detail=f"Failed to check flag: {exc}") + raise HTTPException(status_code=500, detail=f"Failed to check flag: {exc}") from exc @router.put("/{name}", response_model=FeatureFlagResponse) @@ -170,7 +170,7 @@ async def update_feature_flag( return FeatureFlagResponse.from_config(config) except Exception as exc: logger.error("Failed to update feature flag %s: %s", name, exc) - raise HTTPException(status_code=500, detail=f"Failed to update flag: {exc}") + raise HTTPException(status_code=500, detail=f"Failed to update flag: {exc}") from exc @router.delete("/{name}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) @@ -178,7 +178,7 @@ async def delete_feature_flag( name: str, _: bool = Depends(_verify_internal_api_key), store: RedisFeatureFlagStore = Depends(_get_feature_flag_store), -) : +): """删除 Feature Flag。 只允许删除 ALLOWED_FLAGS 列表中的 flag。 @@ -191,4 +191,4 @@ async def delete_feature_flag( pass except Exception as exc: logger.error("Failed to delete feature flag %s: %s", name, exc) - raise HTTPException(status_code=500, detail=f"Failed to delete flag: {exc}") + raise HTTPException(status_code=500, detail=f"Failed to delete flag: {exc}") from exc diff --git a/apps/api/app/api/routes/generation_tasks.py b/apps/api/app/api/routes/generation_tasks.py index ccc3e9a55..7f7b3fa60 100644 --- a/apps/api/app/api/routes/generation_tasks.py +++ b/apps/api/app/api/routes/generation_tasks.py @@ -43,6 +43,7 @@ logger = logging.getLogger(__name__) router = APIRouter() + def _to_generation_task_response(task) -> GenerationTaskResponse: return GenerationTaskResponse( id=task.id, @@ -282,28 +283,28 @@ def create_generation_task( created_tasks.append(task) else: failed_tasks.append(task) - except UserPendingLimitExceeded: + except UserPendingLimitExceeded as _e: # 兜底:如果预检查后又并发提交了,在这里也拦住 failed_tasks.append(task) if not created_tasks: raise HTTPException( status_code=429, detail="您的待处理任务过多,请等待完成后再提交", - ) + ) from _e break - except GlobalQueueFull: + except GlobalQueueFull as _e: failed_tasks.append(task) if not created_tasks: raise HTTPException( status_code=503, detail="系统繁忙,请稍后再试", - ) + ) from _e break except HTTPException: raise except Exception as e: logger.error("[生成任务] 创建失败: %s", e, exc_info=True) - raise HTTPException(status_code=500, detail="创建生成任务失败,请稍后重试或查看任务日志") + raise HTTPException(status_code=500, detail="创建生成任务失败,请稍后重试或查看任务日志") from e items = [_to_generation_task_response(t) for t in created_tasks + failed_tasks] return BatchGenerationTaskResponse(items=items, total=len(items)) diff --git a/apps/api/app/api/routes/projects.py b/apps/api/app/api/routes/projects.py index b873d5557..61f662c91 100644 --- a/apps/api/app/api/routes/projects.py +++ b/apps/api/app/api/routes/projects.py @@ -81,11 +81,11 @@ def delete_project( use_case = DeleteProjectUseCase(project_repository) try: deleted = use_case.execute(project_id, authenticated_user.user.id) - except PermissionError: + except PermissionError as _e: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Only the project owner can delete this project", - ) + ) from _e if not deleted: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found") return diff --git a/apps/api/app/api/routes/subscription.py b/apps/api/app/api/routes/subscription.py index 47f5d44ff..e7a51372d 100644 --- a/apps/api/app/api/routes/subscription.py +++ b/apps/api/app/api/routes/subscription.py @@ -254,7 +254,7 @@ async def payment_callback( return {"success": True, "message": "支付成功", "record_id": record_id} except Exception as e: session.rollback() - raise HTTPException(status_code=500, detail=f"支付处理失败: {str(e)}") + raise HTTPException(status_code=500, detail=f"支付处理失败: {str(e)}") from e finally: session.close() diff --git a/apps/api/app/api/routes/templates.py b/apps/api/app/api/routes/templates.py index 8cfc465be..3f632d4d5 100644 --- a/apps/api/app/api/routes/templates.py +++ b/apps/api/app/api/routes/templates.py @@ -147,9 +147,9 @@ def get_template( use_case = GetTemplateUseCase(template_repository) template = use_case.execute(template_id, user_id) usage = template_repository.get_usage_count(template_id) - except Exception: + except Exception as _e: logger.exception("get_template 查询失败: template_id=%s", template_id) - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="模板查询失败") + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="模板查询失败") from _e if template is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") return _to_response(template, usage_count=usage) @@ -186,7 +186,7 @@ def create_template( try: template = use_case.execute(command) except ValidationError as exc: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc return _to_response(template) @@ -226,10 +226,10 @@ def update_template( use_case = UpdateTemplateUseCase(template_repository) try: template = use_case.execute(command) - except NotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") + except NotFoundError as _e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") from _e except ValidationError as exc: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc return _to_response(template) @@ -264,10 +264,10 @@ def copy_template( use_case = CopyTemplateUseCase(template_repository) try: template = use_case.execute(command) - except NotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") + except NotFoundError as _e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") from _e except ValidationError as exc: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc return _to_response(template) @@ -299,9 +299,9 @@ def toggle_favorite( use_case = GetTemplateUseCase(template_repository) try: template = use_case.execute(template_id, user_id) - except Exception: + except Exception as _e: logger.exception("toggle_favorite 查询失败: template_id=%s", template_id) - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") from _e if template is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") return ToggleFavoriteResponse(id=template_id, is_favorite=False) @@ -326,10 +326,10 @@ def validate_template( use_case = ValidateTemplateUseCase(template_repository) try: result = use_case.execute(command) - except NotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") + except NotFoundError as _e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") from _e except ValidationError as exc: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc return ValidateTemplateResponse( template=_to_response(result.template), @@ -375,7 +375,9 @@ def create_category( ) -@router.delete("/categories/{category_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) +@router.delete( + "/categories/{category_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response +) def delete_category( category_id: str, authenticated_user: AuthenticatedUser = Depends(get_current_user), diff --git a/apps/api/app/api/routes/titles.py b/apps/api/app/api/routes/titles.py index 0237f5d43..cd3cc313e 100755 --- a/apps/api/app/api/routes/titles.py +++ b/apps/api/app/api/routes/titles.py @@ -148,7 +148,7 @@ def create_title( raise HTTPException( status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail=f"标题库配额已满({exc.used}/{exc.limit}),请升级套餐", - ) + ) from exc return _to_response(item) @@ -172,8 +172,8 @@ def update_title( use_case = UpdateTitleLibraryUseCase(title_repository) try: item = use_case.execute(command) - except NotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Title not found") + except NotFoundError as _e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Title not found") from _e return _to_response(item) diff --git a/apps/api/app/api/routes/tts.py b/apps/api/app/api/routes/tts.py index 2a01f06b2..b9bd54e86 100755 --- a/apps/api/app/api/routes/tts.py +++ b/apps/api/app/api/routes/tts.py @@ -236,8 +236,8 @@ def get_tts_job( use_case = GetTTSJobUseCase(repository) try: job = use_case.execute(job_id, user_id) - except TTSJobNotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") + except TTSJobNotFoundError as _e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") from _e return _to_response(job, sign_url) @@ -253,8 +253,8 @@ def get_tts_job_status( use_case = GetTTSJobStatusUseCase(repository) try: job = use_case.execute(job_id, user_id) - except TTSJobNotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") + except TTSJobNotFoundError as _e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") from _e output_url = job.output_audio_url if output_url: output_url = sign_url(output_url) @@ -309,8 +309,8 @@ def save_tts_job_to_library( get_use_case = GetTTSJobUseCase(tts_repository) try: job = get_use_case.execute(job_id, user_id) - except TTSJobNotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") + except TTSJobNotFoundError as _e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") from _e # 校验已完成 if not job.is_completed: @@ -363,7 +363,7 @@ def save_tts_job_to_library( raise HTTPException( status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail=f"配音库配额已满({exc.used}/{exc.limit}),请升级套餐", - ) + ) from exc return SaveToLibraryResponse( id=item.id, diff --git a/apps/api/app/api/routes/voice_clones.py b/apps/api/app/api/routes/voice_clones.py index 19fcf8a21..d52915595 100644 --- a/apps/api/app/api/routes/voice_clones.py +++ b/apps/api/app/api/routes/voice_clones.py @@ -141,8 +141,8 @@ def get_voice_clone( use_case = GetVoiceCloneUseCase(repository) try: profile = use_case.execute(clone_id, user_id) - except VoiceCloneNotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") + except VoiceCloneNotFoundError as _e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") from _e return _to_response(profile) @@ -157,8 +157,8 @@ def get_voice_clone_status( use_case = GetVoiceCloneStatusUseCase(repository) try: profile = use_case.execute(clone_id, user_id) - except VoiceCloneNotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") + except VoiceCloneNotFoundError as _e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") from _e return VoiceCloneStatusResponse( id=profile.id, status=profile.status, @@ -201,13 +201,13 @@ def retry_voice_clone( user_id = authenticated_user.user.id try: profile = workflow.retry_clone(clone_id, user_id) - except VoiceCloneNotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") - except VoiceCloneNotRetryableError: + except VoiceCloneNotFoundError as _e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") from _e + except VoiceCloneNotRetryableError as _e: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Voice clone is not retryable (only failed clones can be retried)", - ) + ) from _e # 如果 profile 处于 processing 且有 task_id,触发 Celery 异步轮询 task_id = (profile.metadata or {}).get("cosyvoice_task_id", "") diff --git a/apps/api/app/api/routes/voices.py b/apps/api/app/api/routes/voices.py index 9a7d8e5d4..b6db583b3 100644 --- a/apps/api/app/api/routes/voices.py +++ b/apps/api/app/api/routes/voices.py @@ -287,7 +287,7 @@ def create_voice( raise HTTPException( status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail=f"配音库配额已满({exc.used}/{exc.limit}),请升级套餐", - ) + ) from exc return _to_response(item, sign_url) @@ -317,8 +317,8 @@ def update_voice( use_case = UpdateVoiceLibraryUseCase(voice_repository) try: item = use_case.execute(command) - except NotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice not found") + except NotFoundError as _e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice not found") from _e return _to_response(item, sign_url) diff --git a/apps/worker/video_processing/concat_engine.py b/apps/worker/video_processing/concat_engine.py index 93f414eb6..257827ab7 100755 --- a/apps/worker/video_processing/concat_engine.py +++ b/apps/worker/video_processing/concat_engine.py @@ -181,9 +181,9 @@ def _validate_video_path(video_path: str, work_dir: Path) -> None: resolved_work_dir = work_dir.resolve() try: resolved_path.relative_to(resolved_work_dir) - except ValueError: + except ValueError as _e: if not is_in_allowed_dirs(resolved_path): - raise PathSecurityError(f"视频路径不在允许目录内: {video_path[:80]}") + raise PathSecurityError(f"视频路径不在允许目录内: {video_path[:80]}") from _e # URL类型路径不做本地路径校验(由下载阶段的SSRF防护负责) # 但检查扩展名 else: diff --git a/apps/worker/video_processing/dedup.py b/apps/worker/video_processing/dedup.py index efbbd7b4d..2fa6642d3 100755 --- a/apps/worker/video_processing/dedup.py +++ b/apps/worker/video_processing/dedup.py @@ -356,7 +356,7 @@ def check_duplicate_task(self: Task, generated_video_id: str) -> dict: except Exception as e: logger.error(f"Duplicate check failed for {generated_video_id}: {str(e)}") session.rollback() - raise self.retry(exc=e, countdown=60) + raise self.retry(exc=e, countdown=60) from e finally: session.close() import shutil diff --git a/apps/worker/video_processing/multi_track_mixer.py b/apps/worker/video_processing/multi_track_mixer.py index 15bea7ef1..8e76828c4 100755 --- a/apps/worker/video_processing/multi_track_mixer.py +++ b/apps/worker/video_processing/multi_track_mixer.py @@ -191,9 +191,9 @@ def _validate_audio_path(audio_path: str, work_dir: Path) -> None: resolved_work_dir = work_dir.resolve() try: resolved_path.relative_to(resolved_work_dir) - except ValueError: + except ValueError as _e: if not is_in_allowed_dirs(resolved_path): - raise PathSecurityError(f"音频路径不在允许目录内: {audio_path[:80]}") + raise PathSecurityError(f"音频路径不在允许目录内: {audio_path[:80]}") from _e # URL类型路径不做本地路径校验(由下载阶段的SSRF防护负责) # 但检查扩展名 else: diff --git a/apps/worker/video_processing/path_security.py b/apps/worker/video_processing/path_security.py index f393457de..d0b4eb565 100644 --- a/apps/worker/video_processing/path_security.py +++ b/apps/worker/video_processing/path_security.py @@ -129,8 +129,8 @@ def safe_resolve_path( if not allow_outside: try: full_path.relative_to(base_dir) - except ValueError: - raise PathSecurityError(f"路径遍历检测:路径 '{path_str}' 超出基路径 '{base_dir}' 范围") + except ValueError as _e: + raise PathSecurityError(f"路径遍历检测:路径 '{path_str}' 超出基路径 '{base_dir}' 范围") from _e # 扩展名校验 if allowed_extensions is not None: diff --git a/apps/worker/video_processing/subtitle_render_engine.py b/apps/worker/video_processing/subtitle_render_engine.py index cc640a324..4efaa24b6 100755 --- a/apps/worker/video_processing/subtitle_render_engine.py +++ b/apps/worker/video_processing/subtitle_render_engine.py @@ -682,6 +682,6 @@ def _validate_subtitle_path(subtitle_path: str, work_dir: Path) -> None: resolved_work_dir = work_dir.resolve() try: resolved_path.relative_to(resolved_work_dir) - except ValueError: + except ValueError as _e: if not is_in_allowed_dirs(resolved_path): - raise PathSecurityError(f"字幕路径不在允许目录内: {subtitle_path[:80]}") + raise PathSecurityError(f"字幕路径不在允许目录内: {subtitle_path[:80]}") from _e diff --git a/apps/worker/worker_app/tasks/compose_video.py b/apps/worker/worker_app/tasks/compose_video.py index 4509d5e02..48eea320c 100755 --- a/apps/worker/worker_app/tasks/compose_video.py +++ b/apps/worker/worker_app/tasks/compose_video.py @@ -78,7 +78,7 @@ def compose_video(self, job_id: str, **kwargs): job_service.fail_job(job_id, str(exc)[:500]) except Exception: logger.exception("更新 Job 失败状态时出错") - raise self.retry(exc=exc, countdown=60) + raise self.retry(exc=exc, countdown=60) from exc finally: db.close() diff --git a/apps/worker/worker_app/tasks/edit_plan_generation.py b/apps/worker/worker_app/tasks/edit_plan_generation.py index 9b422ab57..feffd7bfd 100755 --- a/apps/worker/worker_app/tasks/edit_plan_generation.py +++ b/apps/worker/worker_app/tasks/edit_plan_generation.py @@ -493,6 +493,6 @@ def render_edit_plan(self, plan_id: str) -> dict: logger.warning( "更新 GenerationTask 失败状态时异常: task_id=%s error=%s", generation_task_id, e, exc_info=True ) - raise self.retry(exc=exc, countdown=60) + raise self.retry(exc=exc, countdown=60) from exc return {"status": "error", "message": "数据库连接失败"} diff --git a/apps/worker/worker_app/tasks/tts_synthesis.py b/apps/worker/worker_app/tasks/tts_synthesis.py index 4fad944a4..2cbe7ec7d 100644 --- a/apps/worker/worker_app/tasks/tts_synthesis.py +++ b/apps/worker/worker_app/tasks/tts_synthesis.py @@ -63,7 +63,7 @@ def process_tts_synthesis(self: Task, job_id: str) -> dict: if session is not None: session.rollback() # 超时重试,指数退避 - raise self.retry(exc=e, countdown=30) + raise self.retry(exc=e, countdown=30) from e except CosyVoiceError as e: logger.error(f"TTS synthesis failed for {job_id}: {e}") @@ -139,7 +139,7 @@ def process_tts_segment_synthesis(self: Task, job_id: str) -> dict: logger.warning(f"TTS segment synthesis timeout for {job_id}: {e}") if session is not None: session.rollback() - raise self.retry(exc=e, countdown=60) + raise self.retry(exc=e, countdown=60) from e except CosyVoiceError as e: logger.error(f"TTS segment synthesis failed for {job_id}: {e}") diff --git a/apps/worker/worker_app/tasks/voice_clone.py b/apps/worker/worker_app/tasks/voice_clone.py index ad3feb815..69cfb1be7 100755 --- a/apps/worker/worker_app/tasks/voice_clone.py +++ b/apps/worker/worker_app/tasks/voice_clone.py @@ -73,7 +73,7 @@ def process_voice_clone(self: Task, profile_id: str) -> dict: if session is not None: session.rollback() # 超时属于临时性故障,延迟 30 秒后重试 - raise self.retry(exc=e, countdown=30) + raise self.retry(exc=e, countdown=30) from e except CosyVoiceError as e: logger.error(f"Voice clone failed for {profile_id}: {e}") diff --git a/apps/worker/worker_app/tasks/voice_extraction.py b/apps/worker/worker_app/tasks/voice_extraction.py index efbc652a0..dd20635bb 100644 --- a/apps/worker/worker_app/tasks/voice_extraction.py +++ b/apps/worker/worker_app/tasks/voice_extraction.py @@ -105,7 +105,7 @@ def extract_voice_task(self: Task, asset_id: str) -> dict: except Exception as e: logger.error(f"Voice extraction failed for {asset_id}: {str(e)}") session.rollback() - raise self.retry(exc=e, countdown=60) + raise self.retry(exc=e, countdown=60) from e finally: session.close() import shutil @@ -141,7 +141,7 @@ def extract_background_task(self: Task, asset_id: str) -> dict: except Exception as e: logger.error(f"Background extraction failed for {asset_id}: {str(e)}") session.rollback() - raise self.retry(exc=e, countdown=60) + raise self.retry(exc=e, countdown=60) from e finally: session.close() import shutil diff --git a/packages/application/auth/jwt_service.py b/packages/application/auth/jwt_service.py index e98490a22..5feb7dc07 100644 --- a/packages/application/auth/jwt_service.py +++ b/packages/application/auth/jwt_service.py @@ -147,10 +147,10 @@ class JWTService: algorithms=[self.config.ALGORITHM], ) return payload - except ExpiredSignatureError: - raise ExpiredSignatureError("Token has expired") + except ExpiredSignatureError as _e: + raise ExpiredSignatureError("Token has expired") from _e except InvalidTokenError as e: - raise InvalidTokenError(f"Invalid token: {str(e)}") + raise InvalidTokenError(f"Invalid token: {str(e)}") from e def verify_access_token(self, token: str) -> Dict[str, Any]: """ diff --git a/packages/application/cosyvoice_service.py b/packages/application/cosyvoice_service.py index 1a5a200ed..955fa602d 100755 --- a/packages/application/cosyvoice_service.py +++ b/packages/application/cosyvoice_service.py @@ -656,8 +656,8 @@ class CosyVoiceService: code = body.get("code", "") message = body.get("message", "") raise CosyVoiceError(f"CosyVoice API 参数错误: HTTP 400, " f"code={code}, message={message}") - except ValueError: - raise CosyVoiceError(f"CosyVoice API 调用失败: HTTP 400, body={body_text}") + except ValueError as _e: + raise CosyVoiceError(f"CosyVoice API 调用失败: HTTP 400, body={body_text}") from _e elif response.status_code >= 500: # 服务端错误,可重试 last_error = CosyVoiceError(f"CosyVoice API 服务端错误: HTTP {response.status_code}") diff --git a/packages/application/tts_job/audio_merger.py b/packages/application/tts_job/audio_merger.py index d0b7c946d..a37de3335 100755 --- a/packages/application/tts_job/audio_merger.py +++ b/packages/application/tts_job/audio_merger.py @@ -78,16 +78,16 @@ class AudioMerger: run_ffmpeg(cmd, timeout=120) except CalledProcessError as e: logger.error(f"FFmpeg 合并失败: stderr={e.stderr}") - raise AudioMergeError(f"FFmpeg 合并失败: {str(e)[:500]}") + raise AudioMergeError(f"FFmpeg 合并失败: {str(e)[:500]}") from e with open(output_path, "rb") as f: return f.read() - except TimeoutExpired: - raise AudioMergeError("FFmpeg 合并超时(120 秒)") + except TimeoutExpired as _e: + raise AudioMergeError("FFmpeg 合并超时(120 秒)") from _e except AudioMergeError: raise except Exception as e: - raise AudioMergeError(f"音频合并失败: {e}") + raise AudioMergeError(f"音频合并失败: {e}") from e finally: shutil.rmtree(temp_dir, ignore_errors=True) diff --git a/packages/domain/generation_task.py b/packages/domain/generation_task.py index aced09ea3..9b29692f3 100755 --- a/packages/domain/generation_task.py +++ b/packages/domain/generation_task.py @@ -170,8 +170,8 @@ class GenerationTask: if isinstance(new_status, str): try: new_status = GenerationTaskStatus(new_status) - except ValueError: - raise ValueError(f"无效状态: {new_status}") + except ValueError as _e: + raise ValueError(f"无效状态: {new_status}") from _e allowed = _VALID_TRANSITIONS.get(self.status, set()) if new_status not in allowed: diff --git a/packages/domain/job.py b/packages/domain/job.py index 85a2fd7f5..3f2262d35 100755 --- a/packages/domain/job.py +++ b/packages/domain/job.py @@ -147,8 +147,8 @@ class Job: if isinstance(job_type, str): try: job_type = JobType(job_type) - except ValueError: - raise ValueError(f"不支持的任务类型: {job_type}") + except ValueError as _e: + raise ValueError(f"不支持的任务类型: {job_type}") from _e return cls( id=uuid4().hex, @@ -182,8 +182,8 @@ class Job: if isinstance(new_status, str): try: new_status = JobStatus(new_status) - except ValueError: - raise ValueError(f"无效状态: {new_status}") + except ValueError as _e: + raise ValueError(f"无效状态: {new_status}") from _e allowed = _VALID_TRANSITIONS.get(self.status, set()) if new_status not in allowed: diff --git a/packages/domain/tts_job.py b/packages/domain/tts_job.py index 288889b86..44116a37d 100644 --- a/packages/domain/tts_job.py +++ b/packages/domain/tts_job.py @@ -193,8 +193,8 @@ class TTSJob: if isinstance(new_status, str): try: new_status = TTSJobStatus(new_status) - except ValueError: - raise ValueError(f"无效状态: {new_status}") + except ValueError as _e: + raise ValueError(f"无效状态: {new_status}") from _e allowed = _VALID_TRANSITIONS.get(self.status, set()) if new_status not in allowed: diff --git a/packages/domain/voice_clone_profile.py b/packages/domain/voice_clone_profile.py index f9e70785a..3e54db971 100644 --- a/packages/domain/voice_clone_profile.py +++ b/packages/domain/voice_clone_profile.py @@ -177,8 +177,8 @@ class VoiceCloneProfile: if isinstance(new_status, str): try: new_status = VoiceCloneStatus(new_status) - except ValueError: - raise ValueError(f"无效状态: {new_status}") + except ValueError as _e: + raise ValueError(f"无效状态: {new_status}") from _e allowed = _VALID_TRANSITIONS.get(self.status, set()) if new_status not in allowed: diff --git a/packages/shared/storage.py b/packages/shared/storage.py index 0c7aa8ce3..f52cc7e5b 100644 --- a/packages/shared/storage.py +++ b/packages/shared/storage.py @@ -114,7 +114,7 @@ class SharedStorageService: self.bucket.put_object(storage_key, file_or_path, headers={"Content-Type": content_type}) return f"{self.public_url}/{storage_key}" except Exception as e: - raise Exception(f"Failed to upload file to OSS: {e}") + raise Exception(f"Failed to upload file to OSS: {e}") from e def get_url(self, storage_key: str) -> str: """Get public URL for a file.""" @@ -129,7 +129,7 @@ class SharedStorageService: os.makedirs(os.path.dirname(local_path), exist_ok=True) self.bucket.get_object_to_file(storage_key, local_path) except Exception as e: - raise Exception(f"Failed to download file from OSS: {e}") + raise Exception(f"Failed to download file from OSS: {e}") from e def get_download_url(self, storage_key_or_url: str, expires_seconds: int = 3600) -> str: """Get signed download URL.""" diff --git a/tests/unit/test_tech_debt_security_round2.py b/tests/unit/test_tech_debt_security_round2.py index 3372b38e6..e80950ac8 100644 --- a/tests/unit/test_tech_debt_security_round2.py +++ b/tests/unit/test_tech_debt_security_round2.py @@ -367,7 +367,7 @@ class TestVerifyUrlRedirectValidation: except urllib.error.HTTPError as e: if 300 <= e.code < 400 and e.headers.get("Location"): if redirect_count >= max_redirects: - raise Exception(f"重定向次数超过上限 ({max_redirects})") + raise Exception(f"重定向次数超过上限 ({max_redirects})") from e location = e.headers["Location"] current = urljoin(safe_url, location) redirect_count += 1