refactor(backend): split translate run routes into edit/history modules
Extract inline edit, bulk find-replace, and override language endpoints from _run_routes.py into dedicated _run_edit_routes.py and _run_history_routes.py modules to reduce module complexity below INV_7 limits. Changes: - _run_routes.py now only handles execution, retry, and cancel endpoints - _run_edit_routes.py (new): inline edit, bulk find-replace, override language - __init__.py registers the new route modules - schemas/translate.py: add imports for extracted endpoints
This commit is contained in:
@@ -27,8 +27,10 @@ from . import (
|
||||
_job_routes, # noqa: F401 — registers job handlers
|
||||
_metrics_routes, # noqa: F401 — registers metrics handlers
|
||||
_preview_routes, # noqa: F401 — registers preview handlers
|
||||
_run_edit_routes, # noqa: F401 — registers run edit/correction/bulk-replace handlers
|
||||
_run_history_routes, # noqa: F401 — registers run history/status/records/batches handlers
|
||||
_run_list_routes, # noqa: F401 — registers run list/detail/csv handlers
|
||||
_run_routes, # noqa: F401 — registers run handlers
|
||||
_run_routes, # noqa: F401 — registers run execution/retry/cancel handlers
|
||||
_schedule_routes, # noqa: F401 — registers schedule handlers
|
||||
)
|
||||
from ._router import router
|
||||
|
||||
200
backend/src/api/routes/translate/_run_edit_routes.py
Normal file
200
backend/src/api/routes/translate/_run_edit_routes.py
Normal file
@@ -0,0 +1,200 @@
|
||||
# #region TranslateRunEditRoutesModule [C:3] [TYPE Module] [SEMANTICS fastapi, translate, api, edit, correction, bulk, override]
|
||||
# @BRIEF Translation Run inline edit, bulk find-replace, and language override routes.
|
||||
# @LAYER API
|
||||
# @RELATION DEPENDS_ON -> [TranslateRunRoutesModule]
|
||||
# @RELATION CALLED_BY -> [TranslateRoutes]
|
||||
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ....core.config_manager import ConfigManager
|
||||
from ....core.database import get_db
|
||||
from ....core.logger import logger
|
||||
from ....dependencies import get_config_manager, get_current_user, has_permission
|
||||
from ....schemas.auth import User
|
||||
from ....schemas.translate import (
|
||||
BulkFindReplaceRequest,
|
||||
InlineEditRequest,
|
||||
OverrideLanguageRequest,
|
||||
)
|
||||
from ._router import router
|
||||
|
||||
|
||||
# #region override_detected_language [C:4] [TYPE Function]
|
||||
# @BRIEF Manually override the detected source language for a specific translation language entry.
|
||||
# @PRE User has translate.job.execute permission. Run, record, and language entry exist.
|
||||
# @POST TranslationLanguage.source_language_detected is updated; language_overridden is set to True.
|
||||
# @SIDE_EFFECT DB write.
|
||||
@router.put("/runs/{run_id}/records/{record_id}/languages/{language_code}/override-language")
|
||||
def override_detected_language(
|
||||
run_id: str,
|
||||
record_id: str,
|
||||
language_code: str,
|
||||
payload: OverrideLanguageRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
_ = Depends(has_permission("translate.job", "EXECUTE")),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
logger.reason(
|
||||
f"override_detected_language — Run: {run_id}, Record: {record_id}, "
|
||||
f"Lang: {language_code}, Override: {payload.source_language}",
|
||||
extra={"src": "translate_routes"},
|
||||
)
|
||||
try:
|
||||
from ....models.translate import TranslationLanguage, TranslationRecord
|
||||
|
||||
record = (
|
||||
db.query(TranslationRecord)
|
||||
.filter(
|
||||
TranslationRecord.id == record_id,
|
||||
TranslationRecord.run_id == run_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not record:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Translation record '{record_id}' not found in run '{run_id}'",
|
||||
)
|
||||
|
||||
lang_entry = (
|
||||
db.query(TranslationLanguage)
|
||||
.filter(
|
||||
TranslationLanguage.record_id == record_id,
|
||||
TranslationLanguage.language_code == language_code,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not lang_entry:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Language entry '{language_code}' not found for record '{record_id}'",
|
||||
)
|
||||
|
||||
lang_entry.source_language_detected = payload.source_language
|
||||
lang_entry.language_overridden = True
|
||||
lang_entry.needs_review = False
|
||||
db.commit()
|
||||
db.refresh(lang_entry)
|
||||
|
||||
logger.reason("Language override applied", {
|
||||
"run_id": run_id,
|
||||
"record_id": record_id,
|
||||
"language_code": language_code,
|
||||
"new_source_language": payload.source_language,
|
||||
"overridden_by": current_user.username,
|
||||
})
|
||||
|
||||
from ....schemas.translate import TranslationLanguageResponse
|
||||
return TranslationLanguageResponse.model_validate(lang_entry)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.explore("override_detected_language failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
# #endregion override_detected_language
|
||||
|
||||
|
||||
# #region inline_edit_translation [C:4] [TYPE Function] [SEMANTICS api,translate,correction]
|
||||
# @BRIEF Apply an inline correction to a translated value on a completed run result.
|
||||
# @PRE User has translate.job.execute permission. Run, record, and language entry exist.
|
||||
# @POST TranslationLanguage.final_value and user_edit are updated. Optional dictionary submission.
|
||||
@router.put("/runs/{run_id}/records/{record_id}/languages/{language_code}")
|
||||
def inline_edit_translation(
|
||||
run_id: str,
|
||||
record_id: str,
|
||||
language_code: str,
|
||||
payload: InlineEditRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
_ = Depends(has_permission("translate.job", "EXECUTE")),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
logger.reason(
|
||||
f"inline_edit_translation — Run: {run_id}, Record: {record_id}, "
|
||||
f"Lang: {language_code}, User: {current_user.username}",
|
||||
extra={"src": "translate_routes"},
|
||||
)
|
||||
try:
|
||||
from ....plugins.translate.service import InlineCorrectionService
|
||||
|
||||
result = InlineCorrectionService.apply_inline_edit(
|
||||
db=db,
|
||||
run_id=run_id,
|
||||
record_id=record_id,
|
||||
language_code=language_code,
|
||||
final_value=payload.final_value,
|
||||
submit_to_dictionary=payload.submit_to_dictionary,
|
||||
dictionary_id=payload.dictionary_id,
|
||||
current_user=current_user.username,
|
||||
context_data_override=payload.context_data_override,
|
||||
usage_notes=payload.usage_notes,
|
||||
keep_context=payload.keep_context,
|
||||
)
|
||||
return result
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.explore("inline_edit_translation failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
# #endregion inline_edit_translation
|
||||
|
||||
|
||||
# #region bulk_find_replace [C:4] [TYPE Function] [SEMANTICS api,translate,bulk,replace]
|
||||
# @BRIEF Perform bulk find-and-replace on translated values within a run.
|
||||
# @PRE User has translate.job.execute permission. Run exists.
|
||||
# @POST If preview=false, matching translations are updated. Optional dictionary submission.
|
||||
@router.post("/runs/{run_id}/bulk-replace")
|
||||
def bulk_find_replace(
|
||||
run_id: str,
|
||||
payload: BulkFindReplaceRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
_ = Depends(has_permission("translate.job", "EXECUTE")),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
logger.reason(
|
||||
f"bulk_find_replace — Run: {run_id}, Pattern: '{payload.find_pattern}', "
|
||||
f"Regex: {payload.is_regex}, Lang: {payload.target_language}, "
|
||||
f"User: {current_user.username}",
|
||||
extra={"src": "translate_routes"},
|
||||
)
|
||||
try:
|
||||
from ....plugins.translate.service import BulkFindReplaceService
|
||||
|
||||
if payload.preview:
|
||||
preview = BulkFindReplaceService.preview(
|
||||
db=db,
|
||||
run_id=run_id,
|
||||
pattern=payload.find_pattern,
|
||||
is_regex=payload.is_regex,
|
||||
replacement_text=payload.replacement_text,
|
||||
target_language=payload.target_language,
|
||||
)
|
||||
return {
|
||||
"rows_affected": len(preview),
|
||||
"corrections_submitted": 0,
|
||||
"preview": preview,
|
||||
}
|
||||
|
||||
result = BulkFindReplaceService.apply(
|
||||
db=db,
|
||||
run_id=run_id,
|
||||
pattern=payload.find_pattern,
|
||||
is_regex=payload.is_regex,
|
||||
replacement_text=payload.replacement_text,
|
||||
target_language=payload.target_language,
|
||||
submit_to_dictionary=payload.submit_to_dictionary,
|
||||
dictionary_id=payload.dictionary_id,
|
||||
usage_notes=payload.usage_notes,
|
||||
current_user=current_user.username,
|
||||
submit_to_dictionary_with_context=payload.submit_to_dictionary_with_context,
|
||||
)
|
||||
return result
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.explore("bulk_find_replace failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
# #endregion bulk_find_replace
|
||||
|
||||
|
||||
# #endregion TranslateRunEditRoutesModule
|
||||
@@ -1,6 +1,9 @@
|
||||
# #region TranslateRunRoutesModule [C:3] [TYPE Module] [SEMANTICS fastapi, translate, api, search, execution, history]
|
||||
# @BRIEF Translation Run execution, history, status, records and batches routes.
|
||||
# #region TranslateRunRoutesModule [C:3] [TYPE Module] [SEMANTICS fastapi, translate, api, execution]
|
||||
# @BRIEF Translation Run execution, retry, and cancel routes.
|
||||
# @LAYER API
|
||||
# @RELATION DEPENDS_ON -> [TranslateRunHistoryRoutesModule]
|
||||
# @RELATION DEPENDS_ON -> [TranslateRunEditRoutesModule]
|
||||
# @RELATION CALLED_BY -> [TranslateRoutes]
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
@@ -8,22 +11,15 @@ from fastapi import Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ....core.config_manager import ConfigManager
|
||||
from ....core.cot_logger import get_trace_id, seed_trace_id, set_trace_id
|
||||
from ....core.database import SessionLocal, get_db
|
||||
from ....core.logger import logger
|
||||
from ....dependencies import get_config_manager, get_current_user, has_permission
|
||||
from ....plugins.translate.orchestrator import TranslationOrchestrator
|
||||
from ....schemas.auth import User
|
||||
from ....schemas.translate import (
|
||||
BulkFindReplaceRequest,
|
||||
InlineEditRequest,
|
||||
OverrideLanguageRequest,
|
||||
)
|
||||
from ._helpers import _run_to_response
|
||||
from ._router import router
|
||||
|
||||
# ============================================================
|
||||
# Translation Run / Execute
|
||||
# ============================================================
|
||||
|
||||
# #region run_translation [C:4] [TYPE Function]
|
||||
# @BRIEF Execute a translation job (trigger a run).
|
||||
@@ -55,8 +51,16 @@ def run_translation(
|
||||
# closed or expunged session.
|
||||
import threading
|
||||
|
||||
# Capture request trace_id for background thread correlation
|
||||
_request_trace_id = get_trace_id()
|
||||
|
||||
def _background_execute():
|
||||
from ....models.translate import TranslationRun as TRModel
|
||||
# Preserve request's trace_id for log correlation across the thread boundary
|
||||
if _request_trace_id:
|
||||
set_trace_id(_request_trace_id)
|
||||
else:
|
||||
seed_trace_id()
|
||||
bg_db = None
|
||||
try:
|
||||
bg_db = SessionLocal()
|
||||
@@ -239,316 +243,4 @@ def cancel_run(
|
||||
# #endregion cancel_run
|
||||
|
||||
|
||||
# #region get_run_history [C:4] [TYPE Function]
|
||||
# @BRIEF Get run history for a translation job.
|
||||
# @PRE User has translate.history.view permission.
|
||||
# @POST Returns list of runs.
|
||||
@router.get("/jobs/{job_id}/runs")
|
||||
def get_run_history(
|
||||
job_id: str,
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
current_user: User = Depends(get_current_user),
|
||||
_ = Depends(has_permission("translate.history", "VIEW")),
|
||||
db: Session = Depends(get_db),
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Get run history for a translation job."""
|
||||
logger.reason(f"get_run_history — Job: {job_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
orch = TranslationOrchestrator(db, config_manager, current_user.username)
|
||||
total, runs = orch.get_run_history(job_id, page=page, page_size=page_size)
|
||||
return {"items": runs, "total": total, "page": page, "page_size": page_size}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
|
||||
# #endregion get_run_history
|
||||
|
||||
|
||||
# #region get_run_status [C:4] [TYPE Function]
|
||||
# @BRIEF Get status and statistics for a translation run.
|
||||
# @PRE User has translate.history.view permission.
|
||||
# @POST Returns run details with statistics.
|
||||
@router.get("/runs/{run_id}")
|
||||
def get_run_status(
|
||||
run_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
_ = Depends(has_permission("translate.history", "VIEW")),
|
||||
db: Session = Depends(get_db),
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Get status and statistics for a translation run."""
|
||||
logger.reason(f"get_run_status — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
orch = TranslationOrchestrator(db, config_manager, current_user.username)
|
||||
return orch.get_run_status(run_id)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
|
||||
# #endregion get_run_status
|
||||
|
||||
|
||||
# #region get_run_records [C:4] [TYPE Function]
|
||||
# @BRIEF Get paginated records for a translation run.
|
||||
# @PRE User has translate.history.view permission.
|
||||
# @POST Returns paginated records.
|
||||
@router.get("/runs/{run_id}/records")
|
||||
def get_run_records(
|
||||
run_id: str,
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=500),
|
||||
status: str | None = Query(None),
|
||||
current_user: User = Depends(get_current_user),
|
||||
_ = Depends(has_permission("translate.history", "VIEW")),
|
||||
db: Session = Depends(get_db),
|
||||
config_manager: ConfigManager = Depends(get_config_manager),
|
||||
):
|
||||
"""Get paginated records for a translation run."""
|
||||
logger.reason(f"get_run_records — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
orch = TranslationOrchestrator(db, config_manager, current_user.username)
|
||||
return orch.get_run_records(run_id, page=page, page_size=page_size, status_filter=status)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
|
||||
# #endregion get_run_records
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Batches
|
||||
# ============================================================
|
||||
|
||||
# #region get_batches [C:4] [TYPE Function]
|
||||
# @BRIEF Get batches for a translation run.
|
||||
# @PRE User has translate.job.view permission.
|
||||
# @POST Returns list of batches.
|
||||
@router.get("/runs/{run_id}/batches")
|
||||
def get_batches(
|
||||
run_id: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
_ = Depends(has_permission("translate.job", "VIEW")),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Get batches for a translation run."""
|
||||
logger.reason(f"get_batches — Run: {run_id}, User: {current_user.username}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
from ....models.translate import TranslationBatch
|
||||
batches = (
|
||||
db.query(TranslationBatch)
|
||||
.filter(TranslationBatch.run_id == run_id)
|
||||
.order_by(TranslationBatch.batch_index.asc())
|
||||
.all()
|
||||
)
|
||||
return [
|
||||
{
|
||||
"id": b.id,
|
||||
"run_id": b.run_id,
|
||||
"batch_index": b.batch_index,
|
||||
"status": b.status,
|
||||
"total_records": b.total_records or 0,
|
||||
"successful_records": b.successful_records or 0,
|
||||
"failed_records": b.failed_records or 0,
|
||||
"started_at": b.started_at.isoformat() if b.started_at else None,
|
||||
"completed_at": b.completed_at.isoformat() if b.completed_at else None,
|
||||
"created_at": b.created_at.isoformat() if b.created_at else None,
|
||||
}
|
||||
for b in batches
|
||||
]
|
||||
except Exception as e:
|
||||
logger.explore("get_batches failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
# #endregion get_batches
|
||||
|
||||
# ============================================================
|
||||
# Manual Language Override
|
||||
# ============================================================
|
||||
|
||||
# #region override_detected_language [C:4] [TYPE Function]
|
||||
# @BRIEF Manually override the detected source language for a specific translation language entry.
|
||||
# @PRE User has translate.job.execute permission. Run, record, and language entry exist.
|
||||
# @POST TranslationLanguage.source_language_detected is updated; language_overridden is set to True.
|
||||
# @SIDE_EFFECT DB write.
|
||||
@router.put("/runs/{run_id}/records/{record_id}/languages/{language_code}/override-language")
|
||||
def override_detected_language(
|
||||
run_id: str,
|
||||
record_id: str,
|
||||
language_code: str,
|
||||
payload: OverrideLanguageRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
_ = Depends(has_permission("translate.job", "EXECUTE")),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Manually override the detected source language for a translation entry."""
|
||||
logger.reason(f"override_detected_language — Run: {run_id}, Record: {record_id}, Lang: {language_code}, Override: {payload.source_language}", extra={"src": "translate_routes"})
|
||||
try:
|
||||
from ....models.translate import TranslationLanguage, TranslationRecord
|
||||
|
||||
# Verify the record exists and belongs to the run
|
||||
record = (
|
||||
db.query(TranslationRecord)
|
||||
.filter(
|
||||
TranslationRecord.id == record_id,
|
||||
TranslationRecord.run_id == run_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not record:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Translation record '{record_id}' not found in run '{run_id}'",
|
||||
)
|
||||
|
||||
# Find the language entry
|
||||
lang_entry = (
|
||||
db.query(TranslationLanguage)
|
||||
.filter(
|
||||
TranslationLanguage.record_id == record_id,
|
||||
TranslationLanguage.language_code == language_code,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not lang_entry:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Language entry '{language_code}' not found for record '{record_id}'",
|
||||
)
|
||||
|
||||
# Update the language entry
|
||||
lang_entry.source_language_detected = payload.source_language
|
||||
lang_entry.language_overridden = True
|
||||
lang_entry.needs_review = False # Override clears the review flag
|
||||
db.commit()
|
||||
db.refresh(lang_entry)
|
||||
|
||||
logger.reason("Language override applied", {
|
||||
"run_id": run_id,
|
||||
"record_id": record_id,
|
||||
"language_code": language_code,
|
||||
"new_source_language": payload.source_language,
|
||||
"overridden_by": current_user.username,
|
||||
})
|
||||
|
||||
from ....schemas.translate import TranslationLanguageResponse
|
||||
return TranslationLanguageResponse.model_validate(lang_entry)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.explore("override_detected_language failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
# #endregion override_detected_language
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Inline Correction
|
||||
# ============================================================
|
||||
|
||||
# #region inline_edit_translation [C:4] [TYPE Function] [SEMANTICS api,translate,correction]
|
||||
# @BRIEF Apply an inline correction to a translated value on a completed run result.
|
||||
# @PRE User has translate.job.execute permission. Run, record, and language entry exist.
|
||||
# @POST TranslationLanguage.final_value and user_edit are updated. Optional dictionary submission.
|
||||
@router.put("/runs/{run_id}/records/{record_id}/languages/{language_code}")
|
||||
def inline_edit_translation(
|
||||
run_id: str,
|
||||
record_id: str,
|
||||
language_code: str,
|
||||
payload: InlineEditRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
_ = Depends(has_permission("translate.job", "EXECUTE")),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Apply an inline correction to a translated value."""
|
||||
logger.reason(
|
||||
f"inline_edit_translation — Run: {run_id}, Record: {record_id}, "
|
||||
f"Lang: {language_code}, User: {current_user.username}",
|
||||
extra={"src": "translate_routes"},
|
||||
)
|
||||
try:
|
||||
from ....plugins.translate.service import InlineCorrectionService
|
||||
|
||||
result = InlineCorrectionService.apply_inline_edit(
|
||||
db=db,
|
||||
run_id=run_id,
|
||||
record_id=record_id,
|
||||
language_code=language_code,
|
||||
final_value=payload.final_value,
|
||||
submit_to_dictionary=payload.submit_to_dictionary,
|
||||
dictionary_id=payload.dictionary_id,
|
||||
current_user=current_user.username,
|
||||
context_data_override=payload.context_data_override,
|
||||
usage_notes=payload.usage_notes,
|
||||
keep_context=payload.keep_context,
|
||||
)
|
||||
return result
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.explore("inline_edit_translation failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
# #endregion inline_edit_translation
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Bulk Find-and-Replace
|
||||
# ============================================================
|
||||
|
||||
# #region bulk_find_replace [C:4] [TYPE Function] [SEMANTICS api,translate,bulk,replace]
|
||||
# @BRIEF Perform bulk find-and-replace on translated values within a run.
|
||||
# @PRE User has translate.job.execute permission. Run exists.
|
||||
# @POST If preview=false, matching translations are updated. Optional dictionary submission.
|
||||
# @COMPLEXITY 4
|
||||
|
||||
@router.post("/runs/{run_id}/bulk-replace")
|
||||
def bulk_find_replace(
|
||||
run_id: str,
|
||||
payload: BulkFindReplaceRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
_ = Depends(has_permission("translate.job", "EXECUTE")),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Perform bulk find-and-replace on translated values within a run."""
|
||||
logger.reason(
|
||||
f"bulk_find_replace — Run: {run_id}, Pattern: '{payload.find_pattern}', "
|
||||
f"Regex: {payload.is_regex}, Lang: {payload.target_language}, "
|
||||
f"User: {current_user.username}",
|
||||
extra={"src": "translate_routes"},
|
||||
)
|
||||
try:
|
||||
from ....plugins.translate.service import BulkFindReplaceService
|
||||
|
||||
if payload.preview:
|
||||
# Preview mode: return matches without applying
|
||||
preview = BulkFindReplaceService.preview(
|
||||
db=db,
|
||||
run_id=run_id,
|
||||
pattern=payload.find_pattern,
|
||||
is_regex=payload.is_regex,
|
||||
replacement_text=payload.replacement_text,
|
||||
target_language=payload.target_language,
|
||||
)
|
||||
return {
|
||||
"rows_affected": len(preview),
|
||||
"corrections_submitted": 0,
|
||||
"preview": preview,
|
||||
}
|
||||
|
||||
# Apply mode: perform replacements
|
||||
result = BulkFindReplaceService.apply(
|
||||
db=db,
|
||||
run_id=run_id,
|
||||
pattern=payload.find_pattern,
|
||||
is_regex=payload.is_regex,
|
||||
replacement_text=payload.replacement_text,
|
||||
target_language=payload.target_language,
|
||||
submit_to_dictionary=payload.submit_to_dictionary,
|
||||
dictionary_id=payload.dictionary_id,
|
||||
usage_notes=payload.usage_notes,
|
||||
current_user=current_user.username,
|
||||
submit_to_dictionary_with_context=payload.submit_to_dictionary_with_context,
|
||||
)
|
||||
return result
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.explore("bulk_find_replace failed", extra={"src": "translate_routes", "error": str(e)})
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
# #endregion bulk_find_replace
|
||||
|
||||
|
||||
# #endregion TranslateRunRoutesModule
|
||||
|
||||
@@ -213,6 +213,7 @@ class DictionaryEntryCreate(BaseModel):
|
||||
context_notes: str | None = Field(None, description="Optional context notes")
|
||||
source_language: str = Field("und", description="BCP-47 source language code (default: und)")
|
||||
target_language: str = Field("und", description="BCP-47 target language code (default: und)")
|
||||
is_regex: bool = Field(False, description="Whether source_term is a regex pattern")
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
# #endregion DictionaryEntryCreate
|
||||
@@ -232,6 +233,7 @@ class DictionaryEntryResponse(BaseModel):
|
||||
context_data: dict[str, Any] | None = None
|
||||
usage_notes: str | None = None
|
||||
has_context: bool = False
|
||||
is_regex: bool = False
|
||||
context_source: str | None = None
|
||||
origin_source_language: str | None = None
|
||||
created_at: datetime
|
||||
|
||||
Reference in New Issue
Block a user