Files
ss-tools/backend/src/plugins/translate/dictionary_entries.py
2026-05-26 09:30:41 +03:00

165 lines
7.2 KiB
Python

# #region DictionaryEntryCRUD [C:3] [TYPE Class] [SEMANTICS dictionary, entries, crud]
# @BRIEF CRUD operations for DictionaryEntry records.
# @RELATION DEPENDS_ON -> [DictionaryEntry]
# @RELATION DEPENDS_ON -> [TerminologyDictionary]
from sqlalchemy import func
from sqlalchemy.orm import Session
from ...core.logger import belief_scope, logger
from ...models.translate import DictionaryEntry, TerminologyDictionary
from .[EXT:internal:_utils] import _normalize_term
from .dictionary_validation import _validate_bcp47
class DictionaryEntryCRUD:
"""CRUD operations for dictionary entries."""
# region add_entry [TYPE Function]
# @PURPOSE: Add an entry to a dictionary with language-pair-aware uniqueness validation.
@staticmethod
def add_entry(
db: Session, dict_id: str, source_term: str, target_term: str,
context_notes: str | None = None,
source_language: str = "und", target_language: str = "und",
) -> DictionaryEntry:
with belief_scope("DictionaryEntryCRUD.add_entry"):
_validate_bcp47(source_language, "source_language")
_validate_bcp47(target_language, "target_language")
normalized = _normalize_term(source_term)
existing = (
db.query(DictionaryEntry)
.filter(
DictionaryEntry.dictionary_id == dict_id,
DictionaryEntry.source_term_normalized == normalized,
DictionaryEntry.source_language == source_language,
DictionaryEntry.target_language == target_language,
)
.first()
)
if existing:
raise ValueError(
f"Duplicate entry: '{source_term}' (lang: {source_language}{target_language}) "
f"already exists in this dictionary (id={existing.id}). "
"Use overwrite or keep_existing conflict mode."
)
logger.reason("Adding dictionary entry", {"dict_id": dict_id, "term": source_term})
entry = DictionaryEntry(
dictionary_id=dict_id,
source_term=source_term.strip(),
source_term_normalized=normalized,
target_term=target_term.strip(),
context_notes=context_notes.strip() if context_notes else None,
source_language=source_language.strip(),
target_language=target_language.strip(),
)
db.add(entry)
db.commit()
db.refresh(entry)
logger.reflect("Entry added", {"entry_id": entry.id})
return entry
# endregion add_entry
# region edit_entry [TYPE Function]
# @PURPOSE: Edit an existing dictionary entry with language-pair-aware duplicate check.
@staticmethod
def edit_entry(
db: Session, entry_id: str, source_term: str | None = None,
target_term: str | None = None, context_notes: str | None = None,
source_language: str | None = None, target_language: str | None = None,
) -> DictionaryEntry:
with belief_scope("DictionaryEntryCRUD.edit_entry"):
entry = db.query(DictionaryEntry).filter(DictionaryEntry.id == entry_id).first()
if not entry:
raise ValueError(f"Entry not found: {entry_id}")
logger.reason("Editing dictionary entry", {"entry_id": entry_id})
if source_language is not None:
_validate_bcp47(source_language, "source_language")
entry.source_language = source_language.strip()
if target_language is not None:
_validate_bcp47(target_language, "target_language")
entry.target_language = target_language.strip()
if source_term is not None:
normalized = _normalize_term(source_term)
existing = (
db.query(DictionaryEntry)
.filter(
DictionaryEntry.dictionary_id == entry.dictionary_id,
DictionaryEntry.source_term_normalized == normalized,
DictionaryEntry.source_language == entry.source_language,
DictionaryEntry.target_language == entry.target_language,
DictionaryEntry.id != entry_id,
)
.first()
)
if existing:
raise ValueError(f"Duplicate entry: '{source_term}' already exists in this dictionary (id={existing.id}).")
entry.source_term = source_term.strip()
entry.source_term_normalized = normalized
if target_term is not None:
entry.target_term = target_term.strip()
if context_notes is not None:
entry.context_notes = context_notes.strip() if context_notes else None
db.commit()
db.refresh(entry)
logger.reflect("Entry updated", {"entry_id": entry.id})
return entry
# endregion edit_entry
# region delete_entry [TYPE Function]
# @PURPOSE: Delete a single dictionary entry.
@staticmethod
def delete_entry(db: Session, entry_id: str) -> None:
with belief_scope("DictionaryEntryCRUD.delete_entry"):
entry = db.query(DictionaryEntry).filter(DictionaryEntry.id == entry_id).first()
if not entry:
raise ValueError(f"Entry not found: {entry_id}")
logger.reason("Deleting dictionary entry", {"entry_id": entry_id})
db.delete(entry)
db.commit()
logger.reflect("Entry deleted", {"entry_id": entry_id})
# endregion delete_entry
# region clear_entries [TYPE Function]
# @PURPOSE: Delete all entries for a dictionary.
@staticmethod
def clear_entries(db: Session, dict_id: str) -> int:
with belief_scope("DictionaryEntryCRUD.clear_entries"):
dictionary = db.query(TerminologyDictionary).filter(TerminologyDictionary.id == dict_id).first()
if not dictionary:
raise ValueError(f"Dictionary not found: {dict_id}")
logger.reason("Clearing all entries", {"dict_id": dict_id})
deleted = db.query(DictionaryEntry).filter(DictionaryEntry.dictionary_id == dict_id).delete()
db.commit()
logger.reflect("Entries cleared", {"dict_id": dict_id, "count": deleted})
return deleted
# endregion clear_entries
# region list_entries [TYPE Function]
# @PURPOSE: List entries for a dictionary with pagination.
@staticmethod
def list_entries(
db: Session, dict_id: str, page: int = 1, page_size: int = 50,
) -> tuple[list[DictionaryEntry], int]:
total = (
db.query(func.count(DictionaryEntry.id))
.filter(DictionaryEntry.dictionary_id == dict_id)
.scalar() or 0
)
entries = (
db.query(DictionaryEntry)
.filter(DictionaryEntry.dictionary_id == dict_id)
.order_by(DictionaryEntry.source_term.asc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
return entries, total
# endregion list_entries
# #endregion DictionaryEntryCRUD