Files
ss-tools/backend/src/plugins/translate/dictionary_crud.py
busya 1e6ec34c2b feat(backend): drop dictionary dialect columns, add allowed_languages
- Removes source_dialect and target_dialect from TerminologyDictionary model,
  schemas, routes, helper serialization, and all tests
- Adds allowed_languages config field (BCP-47 language codes) to GlobalSettings
  with validation, consolidated settings response, and API endpoint
- Adds alembic migration f1a2b3c4d5e6 to drop the two columns
2026-06-03 11:48:03 +03:00

130 lines
5.3 KiB
Python

# #region DictionaryCRUD [C:3] [TYPE Class] [SEMANTICS dictionary, crud, terminology]
# @BRIEF CRUD operations for TerminologyDictionary records.
# @RELATION DEPENDS_ON -> [TerminologyDictionary]
# @RELATION DEPENDS_ON -> [TranslationJob]
# @RELATION DEPENDS_ON -> [TranslationJobDictionary]
from sqlalchemy import func
from sqlalchemy.orm import Session
from ...core.logger import belief_scope, logger
from ...models.translate import (
DictionaryEntry,
TerminologyDictionary,
TranslationJob,
TranslationJobDictionary,
)
class DictionaryCRUD:
"""CRUD operations for terminology dictionaries."""
# region create_dictionary [TYPE Function]
# @PURPOSE: Create a new terminology dictionary.
@staticmethod
def create_dictionary(
db: Session, name: str,
created_by: str | None = None, description: str | None = None,
is_active: bool = True,
) -> TerminologyDictionary:
with belief_scope("DictionaryCRUD.create_dictionary"):
logger.reason("Creating dictionary", {"name": name})
dictionary = TerminologyDictionary(
name=name, description=description,
is_active=is_active, created_by=created_by,
)
db.add(dictionary)
db.commit()
db.refresh(dictionary)
logger.reflect("Dictionary created", {"id": dictionary.id})
return dictionary
# endregion create_dictionary
# region update_dictionary [TYPE Function]
# @PURPOSE: Update an existing terminology dictionary.
@staticmethod
def update_dictionary(
db: Session, dict_id: str, name: str | None = None,
description: str | None = None, is_active: bool | None = None,
) -> TerminologyDictionary:
with belief_scope("DictionaryCRUD.update_dictionary"):
dictionary = db.query(TerminologyDictionary).filter(TerminologyDictionary.id == dict_id).first()
if not dictionary:
raise ValueError(f"Dictionary not found: {dict_id}")
logger.reason("Updating dictionary", {"id": dict_id})
if name is not None:
dictionary.name = name
if description is not None:
dictionary.description = description
if is_active is not None:
dictionary.is_active = is_active
db.commit()
db.refresh(dictionary)
logger.reflect("Dictionary updated", {"id": dictionary.id})
return dictionary
# endregion update_dictionary
# region delete_dictionary [TYPE Function]
# @PURPOSE: Delete a dictionary, blocked if attached to active/scheduled jobs.
@staticmethod
def delete_dictionary(db: Session, dict_id: str) -> None:
with belief_scope("DictionaryCRUD.delete_dictionary"):
dictionary = db.query(TerminologyDictionary).filter(TerminologyDictionary.id == dict_id).first()
if not dictionary:
raise ValueError(f"Dictionary not found: {dict_id}")
blocked_statuses = ["ACTIVE", "READY", "RUNNING", "SCHEDULED"]
attached_jobs = (
db.query(TranslationJobDictionary)
.filter(
TranslationJobDictionary.dictionary_id == dict_id,
TranslationJobDictionary.job_id.in_(
db.query(TranslationJob.id).filter(TranslationJob.status.in_(blocked_statuses))
),
)
.count()
)
if attached_jobs > 0:
logger.explore("Delete blocked: dictionary attached to active jobs", {"dict_id": dict_id, "jobs": attached_jobs})
raise ValueError(
f"Cannot delete dictionary attached to {attached_jobs} active/scheduled job(s). "
"Remove dictionary associations from jobs first."
)
logger.reason("Deleting dictionary", {"id": dict_id})
db.query(DictionaryEntry).filter(DictionaryEntry.dictionary_id == dict_id).delete()
db.query(TranslationJobDictionary).filter(TranslationJobDictionary.dictionary_id == dict_id).delete()
db.delete(dictionary)
db.commit()
logger.reflect("Dictionary deleted", {"id": dict_id})
# endregion delete_dictionary
# region get_dictionary [TYPE Function]
# @PURPOSE: Get a single dictionary by ID.
@staticmethod
def get_dictionary(db: Session, dict_id: str) -> TerminologyDictionary:
dictionary = db.query(TerminologyDictionary).filter(TerminologyDictionary.id == dict_id).first()
if not dictionary:
raise ValueError(f"Dictionary not found: {dict_id}")
return dictionary
# endregion get_dictionary
# region list_dictionaries [TYPE Function]
# @PURPOSE: List dictionaries with pagination.
@staticmethod
def list_dictionaries(
db: Session, page: int = 1, page_size: int = 20,
) -> tuple[list[TerminologyDictionary], int]:
total = db.query(func.count(TerminologyDictionary.id)).scalar() or 0
dictionaries = (
db.query(TerminologyDictionary)
.order_by(TerminologyDictionary.created_at.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
return dictionaries, total
# endregion list_dictionaries
# #endregion DictionaryCRUD