Spaces:
Sleeping
Sleeping
File size: 9,624 Bytes
86c402d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
"""
Базовый абстрактный класс для всех сущностей с поддержкой триплетного подхода.
"""
import uuid
from abc import abstractmethod
from dataclasses import dataclass, field, fields
from uuid import UUID
@dataclass
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
name: str
text: str
metadata: dict # JSON с метаданными
in_search_text: str | None = None
source_id: UUID | None = None
target_id: UUID | None = None
number_in_relation: int | None = None
type: str = field(default_factory=lambda: "Entity")
def __post_init__(self):
if self.id is None:
self.id = uuid.uuid4()
# Проверяем корректность полей связи
if (self.source_id is not None and self.target_id is None) or \
(self.source_id is None and self.target_id is not None):
raise ValueError("source_id и target_id должны быть либо оба указаны, либо оба None")
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':
"""
Сериализует сущность в простейшую форму сущности, передавая все дополнительные поля в метаданные.
"""
# Получаем список полей базового класса
known_fields = {field.name for field in fields(LinkerEntity)}
# Получаем все атрибуты текущего объекта
dict_entity = {}
for attr_name in dir(self):
# Пропускаем служебные атрибуты, методы и уже известные поля
if (
attr_name.startswith('_')
or attr_name in known_fields
or callable(getattr(self, attr_name))
):
continue
# Добавляем дополнительные поля в словарь
dict_entity[attr_name] = getattr(self, attr_name)
# Преобразуем имена дополнительных полей, добавляя префикс "_"
dict_entity = {f'_{name}': value for name, value in dict_entity.items()}
# Объединяем с существующими метаданными
dict_entity = {**dict_entity, **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=dict_entity,
source_id=self.source_id,
target_id=self.target_id,
number_in_relation=self.number_in_relation,
type=result_type,
)
@classmethod
@abstractmethod
def deserialize(cls, data: 'LinkerEntity') -> 'Self':
"""
Десериализует сущность из простейшей формы сущности, учитывая все дополнительные поля в метаданных.
"""
raise NotImplementedError(
f"Метод deserialize для класса {cls.__class__.__name__} не реализован"
)
# Реестр для хранения всех наследников LinkerEntity
_entity_classes = {}
@classmethod
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
@classmethod
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(data)
except (AttributeError, NotImplementedError) as e:
# Если метод не реализован, возвращаем исходную сущность
return data
# Если тип не найден в реестре, просто возвращаем исходную сущность
# Больше не используем опасное сканирование sys.modules
return data
# Декоратор для регистрации производных классов
def register_entity(cls):
"""
Декоратор для регистрации классов-наследников LinkerEntity.
Пример использования:
@register_entity
class MyEntity(LinkerEntity):
type = "my_entity"
Args:
cls: Класс, который нужно зарегистрировать
Returns:
Исходный класс (без изменений)
"""
# Регистрируем класс в реестр, используя его имя или указанный тип
entity_type = getattr(cls, 'type', cls.__name__)
LinkerEntity._entity_classes[entity_type] = cls
return cls
|