Spaces:
Sleeping
Sleeping
File size: 9,899 Bytes
86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 86c402d 744a170 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 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
"""
Базовый абстрактный класс для всех сущностей с поддержкой триплетного подхода.
"""
import logging
import uuid
from dataclasses import dataclass, field, fields
from uuid import UUID
logger = logging.getLogger(__name__)
@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 = 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
@property
def owner_id(self) -> UUID | None:
"""
Возвращает идентификатор владельца сущности.
"""
if self.is_link():
return None
return self.target_id
@owner_id.setter
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 = {}
@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_to_me(data)
except Exception as e:
logger.error(f"Ошибка при вызове _deserialize_to_me для {entity_type}: {e}", exc_info=True)
return data
return data
@classmethod
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
|