Spaces:
Runtime error
Runtime error
| """ | |
| Базовый абстрактный класс для всех сущностей с поддержкой триплетного подхода. | |
| """ | |
| import logging | |
| import uuid | |
| from dataclasses import dataclass, field, fields | |
| from uuid import UUID | |
| logger = logging.getLogger(__name__) | |
| class LinkerEntity: | |
| """ | |
| Общий класс для всех сущностей в системе извлечения и сборки. | |
| Поддерживает триплетный подход, где каждая сущность может опционально связывать две другие сущности. | |
| Attributes: | |
| id (UUID): Уникальный идентификатор сущности. | |
| name (str): Название сущности. | |
| text (str): Текстое представление сущности. | |
| in_search_text (str | None): Текст для поиска. Если задан, используется в __str__, иначе используется обычное представление. | |
| metadata (dict): Метаданные сущности. | |
| source_id (UUID | None): Опциональный идентификатор исходной сущности. | |
| Если указан, эта сущность является связью. | |
| target_id (UUID | None): Опциональный идентификатор целевой сущности. | |
| Если указан, эта сущность является связью. | |
| number_in_relation (int | None): Используется в случае связей один-ко-многим, | |
| указывает номер целевой сущности в списке. | |
| type (str): Тип сущности. | |
| """ | |
| id: UUID = field(default_factory=uuid.uuid4) | |
| name: str = field(default="") | |
| text: str = field(default="") | |
| metadata: dict = field(default_factory=dict) | |
| in_search_text: str | None = None | |
| source_id: UUID | None = None | |
| target_id: UUID | None = None | |
| number_in_relation: int | None = None | |
| groupper: str | None = None | |
| type: str | None = None | |
| def owner_id(self) -> UUID | None: | |
| """ | |
| Возвращает идентификатор владельца сущности. | |
| """ | |
| if self.is_link(): | |
| return None | |
| return self.target_id | |
| def owner_id(self, value: UUID | None): | |
| """ | |
| Устанавливает идентификатор владельца сущности. | |
| """ | |
| if self.is_link(): | |
| raise ValueError("Связь не может иметь владельца") | |
| self.target_id = value | |
| def __post_init__(self): | |
| if self.id is None: | |
| self.id = uuid.uuid4() | |
| if self.type is None: | |
| self.type = self.__class__.__name__ | |
| def is_link(self) -> bool: | |
| """ | |
| Проверяет, является ли сущность связью (имеет и source_id, и target_id). | |
| Returns: | |
| bool: True, если сущность является связью, иначе False | |
| """ | |
| return self.source_id is not None and self.target_id is not None | |
| def __str__(self) -> str: | |
| """ | |
| Возвращает строковое представление сущности. | |
| Если задан in_search_text, возвращает его, иначе возвращает стандартное представление. | |
| """ | |
| if self.in_search_text is not None: | |
| return self.in_search_text | |
| return f"{self.name}: {self.text}" | |
| def __eq__(self, other: 'LinkerEntity') -> bool: | |
| """ | |
| Сравнивает текущую сущность с другой. | |
| Args: | |
| other: Другая сущность для сравнения | |
| Returns: | |
| bool: True если сущности совпадают, иначе False | |
| """ | |
| if not isinstance(other, self.__class__): | |
| return False | |
| basic_equality = ( | |
| self.id == other.id | |
| and self.name == other.name | |
| and self.text == other.text | |
| and self.type == other.type | |
| ) | |
| # Если мы имеем дело со связями, также проверяем поля связи | |
| if self.is_link() or other.is_link(): | |
| return ( | |
| basic_equality | |
| and self.source_id == other.source_id | |
| and self.target_id == other.target_id | |
| ) | |
| return basic_equality | |
| def serialize(self) -> 'LinkerEntity': | |
| """ | |
| Сериализует сущность в базовый класс `LinkerEntity`, сохраняя все дополнительные поля в метаданные. | |
| """ | |
| base_fields = {f.name for f in fields(LinkerEntity)} | |
| current_fields = {f.name for f in fields(self.__class__)} | |
| extra_field_names = current_fields - base_fields | |
| # Собираем только дополнительные поля, определенные в подклассе | |
| extra_fields_dict = {name: getattr(self, name) for name in extra_field_names} | |
| # Преобразуем имена дополнительных полей, добавляя префикс "_" | |
| prefixed_extra_fields = { | |
| f'_{name}': value for name, value in extra_fields_dict.items() | |
| } | |
| # Объединяем с существующими метаданными (если они были установлены вручную) | |
| final_metadata = {**prefixed_extra_fields, **self.metadata} | |
| result_type = self.type | |
| if result_type == "Entity": | |
| result_type = self.__class__.__name__ | |
| # Создаем базовый объект LinkerEntity | |
| return LinkerEntity( | |
| id=self.id, | |
| name=self.name, | |
| text=self.text, | |
| in_search_text=self.in_search_text, | |
| metadata=final_metadata, # Используем собранные метаданные | |
| source_id=self.source_id, | |
| target_id=self.target_id, | |
| number_in_relation=self.number_in_relation, | |
| groupper=self.groupper, | |
| type=result_type, | |
| ) | |
| def deserialize(self) -> 'LinkerEntity': | |
| """ | |
| Десериализует сущность в нужный тип на основе поля type. | |
| """ | |
| return self._deserialize(self) | |
| # Реестр для хранения всех наследников LinkerEntity | |
| _entity_classes = {} | |
| def register_entity_class(cls, entity_class): | |
| """ | |
| Регистрирует класс-наследник в реестре. | |
| Args: | |
| entity_class: Класс для регистрации | |
| """ | |
| entity_type = entity_class.__name__ | |
| cls._entity_classes[entity_type] = entity_class | |
| if hasattr(entity_class, 'type') and isinstance(entity_class.type, str): | |
| cls._entity_classes[entity_class.type] = entity_class | |
| def _deserialize(cls, data: 'LinkerEntity') -> 'LinkerEntity': | |
| """ | |
| Десериализует сущность в нужный тип на основе поля type. | |
| Args: | |
| data: Сериализованная сущность типа LinkerEntity | |
| Returns: | |
| Десериализованная сущность правильного типа | |
| """ | |
| # Получаем тип сущности | |
| entity_type = data.type | |
| # Проверяем реестр классов | |
| if entity_type in cls._entity_classes: | |
| try: | |
| return cls._entity_classes[entity_type]._deserialize_to_me(data) | |
| except Exception as e: | |
| logger.error(f"Ошибка при вызове _deserialize_to_me для {entity_type}: {e}", exc_info=True) | |
| return data | |
| return data | |
| def _deserialize_to_me(cls, data: 'LinkerEntity') -> 'LinkerEntity': | |
| """ | |
| Десериализует сущность в нужный тип на основе поля type. | |
| """ | |
| return cls( | |
| id=data.id, | |
| name=data.name, | |
| text=data.text, | |
| in_search_text=data.in_search_text, | |
| metadata=data.metadata, | |
| source_id=data.source_id, | |
| target_id=data.target_id, | |
| number_in_relation=data.number_in_relation, | |
| type=data.type, | |
| groupper=data.groupper, | |
| ) | |
| # Алиасы для удобства | |
| Link = LinkerEntity | |
| Entity = LinkerEntity | |
| # Декоратор для регистрации производных классов | |
| def register_entity(cls): | |
| """ | |
| Декоратор для регистрации классов-наследников LinkerEntity. | |
| Пример использования: | |
| @register_entity | |
| class MyEntity(LinkerEntity): | |
| type = "my_entity" | |
| Args: | |
| cls: Класс, который нужно зарегистрировать | |
| Returns: | |
| Исходный класс (без изменений) | |
| """ | |
| # Регистрируем класс в реестр, используя его имя или указанный тип | |
| entity_type = cls.__name__ | |
| LinkerEntity._entity_classes[entity_type] = cls | |
| if hasattr(cls, 'type') and isinstance(cls.type, str): | |
| LinkerEntity._entity_classes[cls.type] = cls | |
| return cls | |