generated from coulomb/repo-seed
relationship persistence, context entities, idempotent asset creation, audit/version handling for relationship changes
This commit is contained in:
@@ -10,6 +10,9 @@ from kontextual_engine.core import (
|
||||
AssetRepresentation,
|
||||
AssetVersion,
|
||||
AuditEvent,
|
||||
ContextEntity,
|
||||
CoreRelationship,
|
||||
IdempotencyRecord,
|
||||
KnowledgeAsset,
|
||||
LifecycleState,
|
||||
MetadataRecord,
|
||||
@@ -24,8 +27,11 @@ class InMemoryAssetRegistryRepository:
|
||||
assets: dict[str, KnowledgeAsset] = field(default_factory=dict)
|
||||
representations: dict[str, AssetRepresentation] = field(default_factory=dict)
|
||||
metadata_records: dict[str, list[MetadataRecord]] = field(default_factory=dict)
|
||||
context_entities: dict[str, ContextEntity] = field(default_factory=dict)
|
||||
relationships: dict[str, CoreRelationship] = field(default_factory=dict)
|
||||
versions: dict[str, list[AssetVersion]] = field(default_factory=dict)
|
||||
audit_events: dict[str, AuditEvent] = field(default_factory=dict)
|
||||
idempotency_records: dict[str, IdempotencyRecord] = field(default_factory=dict)
|
||||
|
||||
def save_actor(self, actor: Actor) -> Actor:
|
||||
self.actors[actor.id] = actor
|
||||
@@ -96,6 +102,50 @@ class InMemoryAssetRegistryRepository:
|
||||
self.get_asset(asset_id)
|
||||
return list(self.metadata_records.get(asset_id, []))
|
||||
|
||||
def save_context_entity(self, entity: ContextEntity) -> ContextEntity:
|
||||
self.context_entities[entity.entity_id] = entity
|
||||
return entity
|
||||
|
||||
def get_context_entity(self, entity_id: str) -> ContextEntity:
|
||||
try:
|
||||
return self.context_entities[entity_id]
|
||||
except KeyError as exc:
|
||||
raise NotFoundError("Context entity not found", details={"entity_id": entity_id}) from exc
|
||||
|
||||
def list_context_entities(self) -> list[ContextEntity]:
|
||||
return sorted(self.context_entities.values(), key=lambda entity: (entity.entity_type.value, entity.name, entity.entity_id))
|
||||
|
||||
def save_relationship(self, relationship: CoreRelationship) -> CoreRelationship:
|
||||
self.get_asset(relationship.source_id)
|
||||
if relationship.target_kind.value == "asset":
|
||||
self.get_asset(relationship.target_id)
|
||||
else:
|
||||
self.get_context_entity(relationship.target_id)
|
||||
self.relationships[relationship.relationship_id] = relationship
|
||||
return relationship
|
||||
|
||||
def get_relationship(self, relationship_id: str) -> CoreRelationship:
|
||||
try:
|
||||
return self.relationships[relationship_id]
|
||||
except KeyError as exc:
|
||||
raise NotFoundError(
|
||||
"Relationship not found",
|
||||
details={"relationship_id": relationship_id},
|
||||
) from exc
|
||||
|
||||
def list_relationships(
|
||||
self,
|
||||
*,
|
||||
source_id: str | None = None,
|
||||
target_id: str | None = None,
|
||||
) -> list[CoreRelationship]:
|
||||
relationships: Iterable[CoreRelationship] = self.relationships.values()
|
||||
if source_id is not None:
|
||||
relationships = [item for item in relationships if item.source_id == source_id]
|
||||
if target_id is not None:
|
||||
relationships = [item for item in relationships if item.target_id == target_id]
|
||||
return sorted(relationships, key=lambda item: (item.source_id, item.target_id, item.predicate, item.relationship_id))
|
||||
|
||||
def save_version(self, version: AssetVersion) -> AssetVersion:
|
||||
self.get_asset(version.asset_id)
|
||||
current = self.versions.setdefault(version.asset_id, [])
|
||||
@@ -115,6 +165,12 @@ class InMemoryAssetRegistryRepository:
|
||||
self.audit_events[event.event_id] = event
|
||||
return event
|
||||
|
||||
def get_audit_event(self, event_id: str) -> AuditEvent:
|
||||
try:
|
||||
return self.audit_events[event_id]
|
||||
except KeyError as exc:
|
||||
raise NotFoundError("Audit event not found", details={"event_id": event_id}) from exc
|
||||
|
||||
def list_audit_events(
|
||||
self,
|
||||
*,
|
||||
@@ -128,3 +184,9 @@ class InMemoryAssetRegistryRepository:
|
||||
events = [event for event in events if event.correlation_id == correlation_id]
|
||||
return sorted(events, key=lambda event: (event.occurred_at, event.event_id))
|
||||
|
||||
def save_idempotency_record(self, record: IdempotencyRecord) -> IdempotencyRecord:
|
||||
self.idempotency_records[record.key] = record
|
||||
return record
|
||||
|
||||
def get_idempotency_record(self, key: str) -> IdempotencyRecord | None:
|
||||
return self.idempotency_records.get(key)
|
||||
|
||||
@@ -12,10 +12,14 @@ from kontextual_engine.core import (
|
||||
AssetRepresentation,
|
||||
AssetVersion,
|
||||
AuditEvent,
|
||||
ContextEntity,
|
||||
CoreRelationship,
|
||||
IdempotencyRecord,
|
||||
KnowledgeAsset,
|
||||
LifecycleState,
|
||||
MetadataRecord,
|
||||
RepresentationKind,
|
||||
RelationshipTargetKind,
|
||||
)
|
||||
from kontextual_engine.errors import NotFoundError, ValidationError
|
||||
|
||||
@@ -183,6 +187,90 @@ class SQLiteAssetRegistryRepository:
|
||||
self.get_asset(asset_id)
|
||||
return [MetadataRecord.from_dict(_loads(row["payload"])) for row in rows]
|
||||
|
||||
def save_context_entity(self, entity: ContextEntity) -> ContextEntity:
|
||||
with self._connect() as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
insert into context_entities (id, entity_type, name, payload)
|
||||
values (?, ?, ?, ?)
|
||||
on conflict(id) do update set
|
||||
entity_type=excluded.entity_type,
|
||||
name=excluded.name,
|
||||
payload=excluded.payload
|
||||
""",
|
||||
(entity.entity_id, entity.entity_type.value, entity.name, _json(entity.to_dict())),
|
||||
)
|
||||
return entity
|
||||
|
||||
def get_context_entity(self, entity_id: str) -> ContextEntity:
|
||||
row = self._one("select payload from context_entities where id = ?", (entity_id,))
|
||||
if row is None:
|
||||
raise NotFoundError("Context entity not found", details={"entity_id": entity_id})
|
||||
return ContextEntity.from_dict(_loads(row["payload"]))
|
||||
|
||||
def list_context_entities(self) -> list[ContextEntity]:
|
||||
rows = self._all("select payload from context_entities order by entity_type, name, id", ())
|
||||
return [ContextEntity.from_dict(_loads(row["payload"])) for row in rows]
|
||||
|
||||
def save_relationship(self, relationship: CoreRelationship) -> CoreRelationship:
|
||||
self.get_asset(relationship.source_id)
|
||||
if relationship.target_kind == RelationshipTargetKind.ASSET:
|
||||
self.get_asset(relationship.target_id)
|
||||
else:
|
||||
self.get_context_entity(relationship.target_id)
|
||||
with self._connect() as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
insert into core_relationships (id, source_id, target_id, target_kind, predicate, payload)
|
||||
values (?, ?, ?, ?, ?, ?)
|
||||
on conflict(id) do update set
|
||||
source_id=excluded.source_id,
|
||||
target_id=excluded.target_id,
|
||||
target_kind=excluded.target_kind,
|
||||
predicate=excluded.predicate,
|
||||
payload=excluded.payload
|
||||
""",
|
||||
(
|
||||
relationship.relationship_id,
|
||||
relationship.source_id,
|
||||
relationship.target_id,
|
||||
relationship.target_kind.value,
|
||||
relationship.predicate,
|
||||
_json(relationship.to_dict()),
|
||||
),
|
||||
)
|
||||
return relationship
|
||||
|
||||
def get_relationship(self, relationship_id: str) -> CoreRelationship:
|
||||
row = self._one("select payload from core_relationships where id = ?", (relationship_id,))
|
||||
if row is None:
|
||||
raise NotFoundError(
|
||||
"Relationship not found",
|
||||
details={"relationship_id": relationship_id},
|
||||
)
|
||||
return CoreRelationship.from_dict(_loads(row["payload"]))
|
||||
|
||||
def list_relationships(
|
||||
self,
|
||||
*,
|
||||
source_id: str | None = None,
|
||||
target_id: str | None = None,
|
||||
) -> list[CoreRelationship]:
|
||||
clauses = []
|
||||
params: list[Any] = []
|
||||
if source_id is not None:
|
||||
clauses.append("source_id = ?")
|
||||
params.append(source_id)
|
||||
if target_id is not None:
|
||||
clauses.append("target_id = ?")
|
||||
params.append(target_id)
|
||||
where = f" where {' and '.join(clauses)}" if clauses else ""
|
||||
rows = self._all(
|
||||
f"select payload from core_relationships{where} order by source_id, target_id, predicate, id",
|
||||
tuple(params),
|
||||
)
|
||||
return [CoreRelationship.from_dict(_loads(row["payload"])) for row in rows]
|
||||
|
||||
def save_version(self, version: AssetVersion) -> AssetVersion:
|
||||
try:
|
||||
with self._connect() as conn:
|
||||
@@ -241,6 +329,12 @@ class SQLiteAssetRegistryRepository:
|
||||
)
|
||||
return event
|
||||
|
||||
def get_audit_event(self, event_id: str) -> AuditEvent:
|
||||
row = self._one("select payload from audit_events where id = ?", (event_id,))
|
||||
if row is None:
|
||||
raise NotFoundError("Audit event not found", details={"event_id": event_id})
|
||||
return AuditEvent.from_dict(_loads(row["payload"]))
|
||||
|
||||
def list_audit_events(
|
||||
self,
|
||||
*,
|
||||
@@ -259,6 +353,34 @@ class SQLiteAssetRegistryRepository:
|
||||
rows = self._all(f"select payload from audit_events{where} order by occurred_at, id", tuple(params))
|
||||
return [AuditEvent.from_dict(_loads(row["payload"])) for row in rows]
|
||||
|
||||
def save_idempotency_record(self, record: IdempotencyRecord) -> IdempotencyRecord:
|
||||
with self._connect() as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
insert into idempotency_records (key, operation, request_hash, status, payload)
|
||||
values (?, ?, ?, ?, ?)
|
||||
on conflict(key) do update set
|
||||
operation=excluded.operation,
|
||||
request_hash=excluded.request_hash,
|
||||
status=excluded.status,
|
||||
payload=excluded.payload
|
||||
""",
|
||||
(
|
||||
record.key,
|
||||
record.operation,
|
||||
record.request_hash,
|
||||
record.status.value,
|
||||
_json(record.to_dict()),
|
||||
),
|
||||
)
|
||||
return record
|
||||
|
||||
def get_idempotency_record(self, key: str) -> IdempotencyRecord | None:
|
||||
row = self._one("select payload from idempotency_records where key = ?", (key,))
|
||||
if row is None:
|
||||
return None
|
||||
return IdempotencyRecord.from_dict(_loads(row["payload"]))
|
||||
|
||||
def _initialize(self) -> None:
|
||||
with self._connect() as conn:
|
||||
conn.executescript(
|
||||
@@ -288,6 +410,20 @@ class SQLiteAssetRegistryRepository:
|
||||
key text not null,
|
||||
payload text not null
|
||||
);
|
||||
create table if not exists context_entities (
|
||||
id text primary key,
|
||||
entity_type text not null,
|
||||
name text not null,
|
||||
payload text not null
|
||||
);
|
||||
create table if not exists core_relationships (
|
||||
id text primary key,
|
||||
source_id text not null references assets(id) on delete cascade,
|
||||
target_id text not null,
|
||||
target_kind text not null,
|
||||
predicate text not null,
|
||||
payload text not null
|
||||
);
|
||||
create table if not exists asset_versions (
|
||||
id text primary key,
|
||||
asset_id text not null references assets(id) on delete cascade,
|
||||
@@ -306,9 +442,19 @@ class SQLiteAssetRegistryRepository:
|
||||
payload text not null,
|
||||
foreign key(actor_id) references actors(id)
|
||||
);
|
||||
create table if not exists idempotency_records (
|
||||
key text primary key,
|
||||
operation text not null,
|
||||
request_hash text not null,
|
||||
status text not null,
|
||||
payload text not null
|
||||
);
|
||||
create index if not exists idx_assets_lifecycle on assets(lifecycle);
|
||||
create index if not exists idx_representations_asset on representations(asset_id);
|
||||
create index if not exists idx_metadata_asset on metadata_records(asset_id);
|
||||
create index if not exists idx_entities_type on context_entities(entity_type);
|
||||
create index if not exists idx_relationships_source on core_relationships(source_id);
|
||||
create index if not exists idx_relationships_target on core_relationships(target_id);
|
||||
create index if not exists idx_versions_asset on asset_versions(asset_id);
|
||||
create index if not exists idx_audit_target on audit_events(target);
|
||||
create index if not exists idx_audit_correlation on audit_events(correlation_id);
|
||||
|
||||
Reference in New Issue
Block a user