change /knowledge endpoints to use ids

This commit is contained in:
phernandez
2024-12-24 18:15:59 -06:00
parent bde381c9e2
commit 0bbcf636f6
21 changed files with 620 additions and 421 deletions
+19 -14
View File
@@ -15,24 +15,29 @@ class EntityOperations(FileOperations):
async def create_entity(self, entity: EntitySchema) -> EntityModel:
"""Create a new entity and write to filesystem."""
logger.debug(f"Creating entity: {entity}")
db_entity = None
file_path = None
try:
# 1. Create entity in DB
db_entity = await self.entity_service.create_entity(entity)
# 2. Write file and get checksum
checksum = await self.write_entity_file(db_entity)
file_path, checksum = await self.write_entity_file(db_entity)
# 3. Update DB with checksum
updated = await self.entity_service.update_entity(db_entity.id, {"checksum": checksum})
updated = await self.entity_service.update_entity(
db_entity.path_id, {"checksum": checksum}
)
return updated
except Exception as e:
# Clean up on any failure
if "db_entity" in locals():
await self.entity_service.delete_entity(db_entity.id) # pyright: ignore [reportPossiblyUnboundVariable]
if "path" in locals():
await self.file_service.delete_file(path) # pyright: ignore [reportUndefinedVariable] # noqa: F821
if db_entity:
await self.entity_service.delete_entity(db_entity.path_id)
if file_path:
await self.file_service.delete_file(file_path)
logger.error(f"Failed to create entity: {e}")
raise
@@ -47,13 +52,13 @@ class EntityOperations(FileOperations):
return created
async def delete_entity(self, entity_id: int) -> bool:
async def delete_entity(self, path_id: str) -> bool:
"""Delete entity and its file."""
logger.debug(f"Deleting entity: {entity_id}")
logger.debug(f"Deleting entity: {path_id}")
try:
# Get entity first for file deletion
entity = await self.entity_service.get_entity(entity_id)
entity = await self.entity_service.get_by_path_id(path_id)
if not entity:
return True # Already deleted
@@ -62,20 +67,20 @@ class EntityOperations(FileOperations):
await self.file_service.delete_file(path)
# Delete from DB (this will cascade to observations/relations)
return await self.entity_service.delete_entity(entity_id)
return await self.entity_service.delete_entity(path_id)
except Exception as e:
logger.error(f"Failed to delete entity: {e}")
raise
async def delete_entities(self, entity_ids: List[int]) -> bool:
async def delete_entities(self, path_ids: List[str]) -> bool:
"""Delete multiple entities and their files."""
logger.debug(f"Deleting entities: {entity_ids}")
logger.debug(f"Deleting entities: {path_ids}")
success = True
# Let errors bubble up
for entity_id in entity_ids:
await self.delete_entity(entity_id)
for path_id in path_ids:
await self.delete_entity(path_id)
success = True
return success
+5 -4
View File
@@ -1,6 +1,7 @@
"""File operations for knowledge service."""
from pathlib import Path
from typing import Tuple
from loguru import logger
@@ -30,24 +31,24 @@ class FileOperations:
"""Generate filesystem path for entity."""
return self.base_path / entity.entity_type / f"{entity.name}.md"
async def write_entity_file(self, entity: EntityModel) -> str:
async def write_entity_file(self, entity: EntityModel) -> Tuple[Path, str]:
"""Write entity to filesystem and return checksum."""
try:
# Ensure we have a fresh entity with all relations loaded
entity = await self.entity_service.get_entity(entity.id)
entity = await self.entity_service.get_by_path_id(entity.path_id)
# Format content
path = self.get_entity_path(entity)
entity_content = await self.knowledge_writer.format_content(entity)
file_content = await self.file_service.add_frontmatter(
id=entity.id,
id=entity.path_id,
content=entity_content,
created=entity.created_at,
updated=entity.updated_at,
)
# Write and get checksum
return await self.file_service.write_file(path, file_content)
return path, await self.file_service.write_file(path, file_content)
except Exception as e:
logger.error(f"Failed to write entity file: {e}")
@@ -18,26 +18,26 @@ class ObservationOperations(RelationOperations):
self.observation_service = observation_service
async def add_observations(
self, entity_id: int, observations: List[str], context: str | None = None
self, path_id: str, observations: List[str], context: str | None = None
) -> EntityModel:
"""Add observations to entity and update its file."""
logger.debug(f"Adding observations to entity {entity_id}")
logger.debug(f"Adding observations to entity {path_id}")
try:
# Get entity to update
entity = await self.entity_service.get_entity(entity_id)
entity = await self.entity_service.get_by_path_id(path_id)
if not entity:
raise EntityNotFoundError(f"Entity not found: {entity_id}")
raise EntityNotFoundError(f"Entity not found: {path_id}")
# Add observations to DB
await self.observation_service.add_observations(entity_id, observations, context)
await self.observation_service.add_observations(entity.id, observations, context)
# Get updated entity
updated_entity = await self.entity_service.get_entity(entity_id)
updated_entity = await self.entity_service.get_by_path_id(path_id)
# Write updated file and checksum
checksum = await self.write_entity_file(entity)
await self.entity_service.update_entity(entity_id, {"checksum": checksum})
await self.entity_service.update_entity(path_id, {"checksum": checksum})
return updated_entity
@@ -45,25 +45,25 @@ class ObservationOperations(RelationOperations):
logger.error(f"Failed to add observations: {e}")
raise
async def delete_observations(self, entity_id: int, observations: List[str]) -> EntityModel:
async def delete_observations(self, path_id: str, observations: List[str]) -> EntityModel:
"""Delete observations from entity and update its file."""
logger.debug(f"Deleting observations from entity {entity_id}")
logger.debug(f"Deleting observations from entity {path_id}")
try:
# Get updated entity
entity = await self.entity_service.get_entity(entity_id)
entity = await self.entity_service.get_by_path_id(path_id)
if not entity:
raise EntityNotFoundError(f"Entity not found: {entity_id}")
raise EntityNotFoundError(f"Entity not found: {path_id}")
# Delete observations from DB
await self.observation_service.delete_observations(entity_id, observations)
await self.observation_service.delete_observations(entity.id, observations)
# Write updated file
checksum = await self.write_entity_file(entity)
await self.entity_service.update_entity(entity_id, {"checksum": checksum})
await self.entity_service.update_entity(path_id, {"checksum": checksum})
# Get final entity with all updates
return await self.entity_service.get_entity(entity_id)
return await self.entity_service.get_by_path_id(path_id)
except Exception as e:
logger.error(f"Failed to delete observations: {e}")
@@ -5,6 +5,7 @@ from typing import Sequence, List, Dict, Any
from loguru import logger
from basic_memory.models import Entity as EntityModel
from basic_memory.models import Relation as RelationModel
from basic_memory.schemas import Relation as RelationSchema
from basic_memory.services.exceptions import EntityNotFoundError
from basic_memory.services.relation_service import RelationService
@@ -21,78 +22,88 @@ class RelationOperations(EntityOperations):
async def create_relations(self, relations: List[RelationSchema]) -> Sequence[EntityModel]:
"""Create relations and return updated entities."""
logger.debug(f"Creating {len(relations)} relations")
created_entities = []
updated_entity_ids = set()
updated_entities = []
entities_to_update = set()
for relation in relations:
for rs in relations:
try:
# Create relation in DB
from_entity = await self.entity_service.get_by_path_id(rs.from_id)
to_entity = await self.entity_service.get_by_path_id(rs.to_id)
relation = RelationModel(
from_id=from_entity.id,
to_id=to_entity.id,
relation_type=rs.relation_type,
context=rs.context,
)
# Create rs in DB
await self.relation_service.create_relation(relation)
# Keep track of entities we need to update
updated_entity_ids.add(relation.from_id)
updated_entity_ids.add(relation.to_id)
entities_to_update.add(rs.from_id)
entities_to_update.add(rs.to_id)
except Exception as e:
logger.error(f"Failed to create relation: {e}")
logger.error(f"Failed to create rs: {e}")
continue
# Get fresh copies of all updated entities
for entity_id in updated_entity_ids:
for path_id in entities_to_update:
try:
# Get fresh entity
entity = await self.entity_service.get_entity(entity_id)
if not entity:
raise EntityNotFoundError(f"Entity not found: {entity_id}")
entity = await self.entity_service.get_by_path_id(path_id)
# Write updated file
checksum = await self.write_entity_file(entity)
updated = await self.entity_service.update_entity(entity_id, {"checksum": checksum})
_, checksum = await self.write_entity_file(entity)
updated = await self.entity_service.update_entity(path_id, {"checksum": checksum})
created_entities.append(updated)
updated_entities.append(updated)
except Exception as e:
logger.error(f"Failed to update entity {entity_id}: {e}")
logger.error(f"Failed to update entity {path_id}: {e}")
continue
return created_entities
# select again to eagerly load all relations
return await self.entity_service.open_nodes([e.path_id for e in updated_entities])
async def delete_relations(self, to_delete: List[Dict[str, Any]]) -> Sequence[EntityModel]:
"""Delete relations and return all updated entities."""
logger.debug(f"Deleting {len(to_delete)} relations")
updated_entity_ids = set()
updated_entities = []
entities_to_update = set()
try:
# Delete relations from DB
for relation in to_delete:
updated_entity_ids.add(relation["from_id"])
updated_entity_ids.add(relation["to_id"])
entities_to_update.add(relation["from_id"])
entities_to_update.add(relation["to_id"])
deleted = await self.relation_service.delete_relations(to_delete)
if not deleted:
logger.warning("No relations were deleted")
# Get fresh copies of all updated entities
updated_entities = []
for entity_id in updated_entity_ids:
for path_id in entities_to_update:
try:
# Get fresh entity
entity = await self.entity_service.get_entity(entity_id)
entity = await self.entity_service.get_by_path_id(path_id)
if not entity:
raise EntityNotFoundError(f"Entity not found: {entity_id}")
raise EntityNotFoundError(f"Entity not found: {path_id}")
# Write updated file
checksum = await self.write_entity_file(entity)
updated = await self.entity_service.update_entity(entity_id, {"checksum": checksum})
updated = await self.entity_service.update_entity(
path_id, {"checksum": checksum}
)
updated_entities.append(updated)
except Exception as e:
logger.error(f"Failed to update entity {entity_id}: {e}")
logger.error(f"Failed to update entity {path_id}: {e}")
continue
return updated_entities
except Exception as e:
logger.error(f"Failed to delete relations: {e}")
raise
raise