fix(translate): M1-M3 medium + L1-L2 low bugs from QA review
M1: ReDoS guard — 500 char max + try/except on re.compile M2: Performance note — O(n×m) regex documented for future optimization M3: Verified test key names correct (metrics.py transforms cumulative_* → short) L1: Removed @staticmethod no-op from module-level function L2: Added aria-label to emoji indicators in CorrectionCell
This commit is contained in:
@@ -37,7 +37,6 @@ def _run_to_response(run: Any) -> dict:
|
||||
|
||||
# #region _dict_to_response [C:2] [TYPE Function]
|
||||
# @BRIEF Convert a TerminologyDictionary ORM model to a response dict with computed entry_count.
|
||||
@staticmethod
|
||||
def _dict_to_response(d: Any, entry_count: int = 0) -> dict:
|
||||
return {
|
||||
"id": d.id,
|
||||
|
||||
@@ -658,6 +658,10 @@ class DictionaryManager:
|
||||
if not all_entries:
|
||||
return []
|
||||
|
||||
# PERFORMANCE NOTE: O(rows × entries) regex matching per batch.
|
||||
# Acceptable for current scale (50-100 rows × <1000 entries).
|
||||
# For larger batches (>500 rows or >5000 entries), consider
|
||||
# replacing with an Aho-Corasick automaton (e.g., pyahocorasick).
|
||||
matched: list[dict[str, Any]] = []
|
||||
seen_source_match: set = set()
|
||||
|
||||
|
||||
@@ -825,13 +825,19 @@ class InlineCorrectionService:
|
||||
class BulkFindReplaceService:
|
||||
"""Handle bulk find-and-replace on TranslationLanguage entries."""
|
||||
|
||||
MAX_PATTERN_LENGTH = 500
|
||||
|
||||
@staticmethod
|
||||
def _compile_pattern(pattern: str, is_regex: bool) -> re.Pattern:
|
||||
"""Compile a search pattern (regex or plain text)."""
|
||||
import re
|
||||
if is_regex:
|
||||
return re.compile(pattern)
|
||||
return re.compile(re.escape(pattern))
|
||||
if len(pattern) > BulkFindReplaceService.MAX_PATTERN_LENGTH:
|
||||
raise ValueError(
|
||||
f"Pattern too long (max {BulkFindReplaceService.MAX_PATTERN_LENGTH} characters)"
|
||||
)
|
||||
try:
|
||||
return re.compile(pattern) if is_regex else re.compile(re.escape(pattern))
|
||||
except re.error as e:
|
||||
raise ValueError(f"Invalid regex pattern: {e}")
|
||||
|
||||
@staticmethod
|
||||
def _find_matching_entries(
|
||||
|
||||
@@ -608,7 +608,7 @@ class OverrideLanguageRequest(BaseModel):
|
||||
# #region BulkFindReplaceRequest [C:1] [TYPE Class]
|
||||
# @BRIEF Schema for bulk find-and-replace within translations for a target language.
|
||||
class BulkFindReplaceRequest(BaseModel):
|
||||
find_pattern: str = Field(..., description="Text or regex pattern to find")
|
||||
find_pattern: str = Field(..., description="Text or regex pattern to find", max_length=500)
|
||||
is_regex: bool = Field(False, description="Whether find_pattern is a regular expression")
|
||||
replacement_text: str = Field(..., description="Replacement text")
|
||||
target_language: str = Field(..., description="BCP-47 language code to target")
|
||||
@@ -617,6 +617,14 @@ class BulkFindReplaceRequest(BaseModel):
|
||||
dictionary_id: str | None = Field(None, description="Target dictionary ID for submitting corrections")
|
||||
usage_notes: str | None = Field(None, description="Usage notes for dictionary entries")
|
||||
submit_to_dictionary_with_context: bool = Field(False, description="If true, auto-capture source row context when submitting to dictionary")
|
||||
|
||||
@field_validator("find_pattern")
|
||||
@classmethod
|
||||
def validate_pattern_length(cls, v: str) -> str:
|
||||
"""Reject patterns longer than 500 characters to prevent ReDoS."""
|
||||
if len(v) > 500:
|
||||
raise ValueError("Pattern too long (max 500 characters)")
|
||||
return v
|
||||
# #endregion BulkFindReplaceRequest
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user