224 lines
7.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Базовый класс для всех LDAP сервисов.
Содержит общую логику, которая используется во всех сервисах:
- Управление состоянием синхронизации (LdapSyncState)
- Логирование операций
- Обработка ошибок
"""
import logging
from typing import Any, Optional
from ...models import LdapSyncState
class BaseService:
"""Базовый класс для всех LDAP сервисов.
Предоставляет общую функциональность:
- _touch_state() - обновление LdapSyncState
- _log_operation() - логирование операций
- _get_logger() - получение логгера для сервиса
"""
def __init__(self):
"""Инициализация базового сервиса."""
self._logger = self._get_logger()
def _get_logger(self) -> logging.Logger:
"""Получает логгер для сервиса.
Returns:
Logger с именем модуля сервиса.
"""
return logging.getLogger(self.__class__.__module__)
def _touch_state(
self,
*,
model: str,
object_pk: int | str,
ldap_dn: Optional[str] = None,
ldap_guid: Optional[str] = None,
last_ldap_modify_ts: Optional[Any] = None,
last_django_modify_ts: Optional[Any] = None,
sync_dir: Optional[str] = None,
) -> LdapSyncState:
"""Обновляет или создает запись состояния синхронизации.
Единая точка входа для всех сервисов для обновления LdapSyncState.
Автоматически логирует изменения при изменении DN.
Args:
model: Тип модели ('employee', 'department', 'group', 'position')
object_pk: Primary key объекта в Django
ldap_dn: Distinguished Name в LDAP
ldap_guid: GUID объекта в LDAP
last_ldap_modify_ts: Время последнего изменения в LDAP
last_django_modify_ts: Время последнего изменения в Django
sync_dir: Направление синхронизации (из SyncDirection)
Returns:
Обновленная запись LdapSyncState
"""
state, created = LdapSyncState.objects.get_or_create(
model=model, object_pk=str(object_pk)
)
# Логируем изменение DN
old_dn = state.ldap_dn if not created else None
if ldap_dn is not None and ldap_dn != old_dn:
self._logger.info(
"LDAP object DN changed",
extra={
"event": "ldap.sync_state.dn_changed",
"ldap_model": model,
"object_id": object_pk,
},
)
# Обновляем поля
if ldap_dn is not None:
state.ldap_dn = ldap_dn
if ldap_guid is not None:
state.ldap_guid = ldap_guid
if last_ldap_modify_ts is not None:
state.last_ldap_modify_ts = last_ldap_modify_ts
if last_django_modify_ts is not None:
state.last_django_modify_ts = last_django_modify_ts
if sync_dir is not None:
state.sync_dir = sync_dir
state.save()
if created:
self._logger.debug(
"LDAP sync state created",
extra={
"event": "ldap.sync_state.created",
"ldap_model": model,
"object_id": object_pk,
"has_ldap_dn": bool(ldap_dn),
},
)
return state
def _get_object_dn(self, model: str, object_pk: int | str) -> str:
"""Получает DN объекта из LdapSyncState.
Args:
model: Тип модели
object_pk: Primary key объекта
Returns:
Distinguished Name объекта
Raises:
ValueError: Если DN не найден или пустой
"""
dn = (
LdapSyncState.objects.filter(model=model, object_pk=str(object_pk))
.values_list("ldap_dn", flat=True)
.first()
)
if not dn:
raise ValueError(f"DN not found for {model}#{object_pk} in LdapSyncState")
return dn
def _log_operation(
self,
operation: str,
*,
model: str,
object_id: Optional[int | str] = None,
dn: Optional[str] = None,
success: bool = True,
error: Optional[Exception] = None,
extra: Optional[dict] = None,
) -> None:
"""Логирует операцию LDAP.
Args:
operation: Название операции (create, update, delete, move, etc.)
model: Тип модели
object_id: ID объекта (если есть)
dn: Distinguished Name (если есть)
success: Успешность операции
error: Ошибка (если была)
extra: Дополнительные данные для логирования
"""
level = logging.INFO if success else logging.ERROR
event = f"ldap.{model}.{operation}.{'succeeded' if success else 'failed'}"
log_fields = {
"event": event,
"ldap_model": model,
"ldap_operation": operation,
"object_id": object_id,
"has_ldap_dn": bool(dn),
"success": success,
"error_type": type(error).__name__ if error else None,
}
if extra:
log_fields.update(extra)
self._logger.log(
level,
"LDAP operation succeeded" if success else "LDAP operation failed",
extra={**log_fields, "event": event},
)
def _safe_execute(
self,
operation: callable,
operation_name: str,
*,
model: str = "unknown",
object_id: Optional[int | str] = None,
dn: Optional[str] = None,
reraise: bool = True,
) -> Any:
"""Безопасно выполняет операцию с логированием.
Args:
operation: Функция для выполнения
operation_name: Название операции для логов
model: Тип модели
object_id: ID объекта
dn: Distinguished Name
reraise: Пробрасывать ли исключение дальше
Returns:
Результат выполнения operation
Raises:
Exception: Если reraise=True и operation вызвала исключение
"""
try:
result = operation()
self._log_operation(
operation_name,
model=model,
object_id=object_id,
dn=dn,
success=True,
)
return result
except Exception as e:
self._log_operation(
operation_name,
model=model,
object_id=object_id,
dn=dn,
success=False,
error=e,
)
if reraise:
raise
return None
__all__ = ["BaseService"]