mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
fix tests
This commit is contained in:
@@ -127,15 +127,7 @@ async def delete_relations(
|
||||
data: DeleteRelationsRequest, knowledge_service: KnowledgeServiceDep
|
||||
) -> CreateEntityResponse:
|
||||
"""Delete relations between entities."""
|
||||
to_delete = [
|
||||
{
|
||||
"from_id": relation.from_id,
|
||||
"to_id": relation.to_id,
|
||||
"relation_type": relation.relation_type,
|
||||
}
|
||||
for relation in data.relations
|
||||
]
|
||||
updated_entities = await knowledge_service.delete_relations(to_delete)
|
||||
updated_entities = await knowledge_service.delete_relations(data.relations)
|
||||
return CreateEntityResponse(
|
||||
entities=[EntityResponse.model_validate(entity) for entity in updated_entities]
|
||||
)
|
||||
|
||||
@@ -79,8 +79,12 @@ class EntityRepository(Repository[Entity]):
|
||||
def get_load_options(self) -> List[LoaderOption]:
|
||||
return [
|
||||
selectinload(Entity.observations),
|
||||
# Load from_relations and both entities for each relation
|
||||
selectinload(Entity.from_relations).selectinload(Relation.from_entity),
|
||||
selectinload(Entity.from_relations).selectinload(Relation.to_entity),
|
||||
# Load to_relations and both entities for each relation
|
||||
selectinload(Entity.to_relations).selectinload(Relation.from_entity),
|
||||
selectinload(Entity.to_relations).selectinload(Relation.to_entity),
|
||||
]
|
||||
|
||||
async def find_by_path_ids(self, path_ids: List[str]) -> Sequence[Entity]:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"""Repository for managing Relation objects."""
|
||||
|
||||
from typing import Sequence, List
|
||||
from sqlalchemy import and_
|
||||
from typing import Sequence, List, Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlalchemy.orm import selectinload, aliased
|
||||
from sqlalchemy.orm.interfaces import LoaderOption
|
||||
|
||||
from basic_memory.models import Relation, Entity
|
||||
@@ -17,6 +17,25 @@ class RelationRepository(Repository[Relation]):
|
||||
def __init__(self, session_maker: async_sessionmaker):
|
||||
super().__init__(session_maker, Relation)
|
||||
|
||||
async def find_relation(self, from_path_id: str, to_path_id: str, relation_type: str) -> Optional[Relation]:
|
||||
"""Find a relation by its from and to path IDs."""
|
||||
from_entity = aliased(Entity)
|
||||
to_entity = aliased(Entity)
|
||||
|
||||
query = (
|
||||
select(Relation)
|
||||
.join(from_entity, Relation.from_id == from_entity.id)
|
||||
.join(to_entity, Relation.to_id == to_entity.id)
|
||||
.where(
|
||||
and_(
|
||||
from_entity.path_id == from_path_id,
|
||||
to_entity.path_id == to_path_id,
|
||||
Relation.relation_type == relation_type
|
||||
)
|
||||
)
|
||||
)
|
||||
return await self.find_one(query)
|
||||
|
||||
async def find_by_entity(self, from_entity_id: int) -> Sequence[Relation]:
|
||||
"""Find all relations from a specific entity."""
|
||||
query = select(Relation).filter(Relation.from_id == from_entity_id)
|
||||
|
||||
@@ -8,3 +8,8 @@ class EntityNotFoundError(Exception):
|
||||
"""Raised when an entity cannot be found"""
|
||||
|
||||
pass
|
||||
|
||||
class EntityCreationError(Exception):
|
||||
"""Raised when an entity cannot be created"""
|
||||
|
||||
pass
|
||||
|
||||
@@ -7,6 +7,7 @@ from loguru import logger
|
||||
from basic_memory.models import Entity as EntityModel
|
||||
from basic_memory.schemas import Entity as EntitySchema
|
||||
from .files import FileOperations
|
||||
from ..exceptions import EntityCreationError, EntityNotFoundError
|
||||
|
||||
|
||||
class EntityOperations(FileOperations):
|
||||
@@ -49,7 +50,7 @@ class EntityOperations(FileOperations):
|
||||
for entity in entities:
|
||||
created_entity = await self.create_entity(entity)
|
||||
created.append(created_entity)
|
||||
|
||||
|
||||
return created
|
||||
|
||||
async def delete_entity(self, path_id: str) -> bool:
|
||||
@@ -59,8 +60,6 @@ class EntityOperations(FileOperations):
|
||||
try:
|
||||
# Get entity first for file deletion
|
||||
entity = await self.entity_service.get_by_path_id(path_id)
|
||||
if not entity:
|
||||
return True # Already deleted
|
||||
|
||||
# Delete file first (it's source of truth)
|
||||
path = self.get_entity_path(entity)
|
||||
@@ -68,7 +67,11 @@ class EntityOperations(FileOperations):
|
||||
|
||||
# Delete from DB (this will cascade to observations/relations)
|
||||
return await self.entity_service.delete_entity(path_id)
|
||||
|
||||
|
||||
except EntityNotFoundError:
|
||||
logger.info(f"Entity not found: {path_id}")
|
||||
return True # Already deleted
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete entity: {e}")
|
||||
raise
|
||||
|
||||
@@ -36,10 +36,11 @@ class ObservationOperations(RelationOperations):
|
||||
updated_entity = await self.entity_service.get_by_path_id(path_id)
|
||||
|
||||
# Write updated file and checksum
|
||||
checksum = await self.write_entity_file(entity)
|
||||
_, checksum = await self.write_entity_file(entity)
|
||||
await self.entity_service.update_entity(path_id, {"checksum": checksum})
|
||||
|
||||
return updated_entity
|
||||
# query to fetch all relations
|
||||
return await self.entity_service.get_by_path_id(path_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to add observations: {e}")
|
||||
@@ -59,7 +60,7 @@ class ObservationOperations(RelationOperations):
|
||||
await self.observation_service.delete_observations(entity.id, observations)
|
||||
|
||||
# Write updated file
|
||||
checksum = await self.write_entity_file(entity)
|
||||
_, checksum = await self.write_entity_file(entity)
|
||||
await self.entity_service.update_entity(path_id, {"checksum": checksum})
|
||||
|
||||
# Get final entity with all updates
|
||||
|
||||
@@ -66,20 +66,26 @@ class RelationOperations(EntityOperations):
|
||||
# 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]:
|
||||
async def delete_relations(self, to_delete: List[RelationSchema]) -> Sequence[EntityModel]:
|
||||
"""Delete relations and return all updated entities."""
|
||||
logger.debug(f"Deleting {len(to_delete)} relations")
|
||||
updated_entities = []
|
||||
entities_to_update = set()
|
||||
|
||||
relations = []
|
||||
|
||||
try:
|
||||
# Delete relations from DB
|
||||
for relation in to_delete:
|
||||
entities_to_update.add(relation["from_id"])
|
||||
entities_to_update.add(relation["to_id"])
|
||||
entities_to_update.add(relation.from_id)
|
||||
entities_to_update.add(relation.to_id)
|
||||
|
||||
relation = await self.relation_service.find_relation(relation.from_id, relation.to_id, relation.relation_type)
|
||||
if relation:
|
||||
relations.append(relation)
|
||||
|
||||
deleted = await self.relation_service.delete_relations(to_delete)
|
||||
if not deleted:
|
||||
# pass Relation models to delete
|
||||
num_deleted = await self.relation_service.delete_relations(relations)
|
||||
if num_deleted == 0:
|
||||
logger.warning("No relations were deleted")
|
||||
|
||||
# Get fresh copies of all updated entities
|
||||
@@ -91,7 +97,7 @@ class RelationOperations(EntityOperations):
|
||||
raise EntityNotFoundError(f"Entity not found: {path_id}")
|
||||
|
||||
# Write updated file
|
||||
checksum = await self.write_entity_file(entity)
|
||||
_, checksum = await self.write_entity_file(entity)
|
||||
updated = await self.entity_service.update_entity(
|
||||
path_id, {"checksum": checksum}
|
||||
)
|
||||
|
||||
@@ -23,6 +23,9 @@ class RelationService(BaseService[RelationRepository]):
|
||||
logger.debug(f"Creating relation: {relation}")
|
||||
return await self.repository.add(relation)
|
||||
|
||||
async def find_relation(self, from_path_id: str, to_path_id: str, relation_type: str) -> Relation:
|
||||
return await self.repository.find_relation(from_path_id, to_path_id, relation_type)
|
||||
|
||||
async def delete_relation(
|
||||
self, from_entity: Entity, to_entity: Entity, relation_type: str
|
||||
) -> bool:
|
||||
@@ -37,20 +40,13 @@ class RelationService(BaseService[RelationRepository]):
|
||||
)
|
||||
return result
|
||||
|
||||
async def delete_relations(self, relations: List[Dict[str, Any]]) -> bool:
|
||||
async def delete_relations(self, relations: List[Relation]) -> int:
|
||||
"""Delete relations matching specified criteria."""
|
||||
logger.debug(f"Deleting {len(relations)} relations")
|
||||
deleted = False
|
||||
for relation in relations:
|
||||
filters = {"from_id": relation["from_id"], "to_id": relation["to_id"]}
|
||||
if "relation_type" in relation:
|
||||
filters["relation_type"] = relation["relation_type"]
|
||||
|
||||
result = await self.repository.delete_by_fields(**filters)
|
||||
if result:
|
||||
deleted = True
|
||||
|
||||
return deleted
|
||||
|
||||
ids = [relation.id for relation in relations]
|
||||
return await self.repository.delete_by_ids(ids)
|
||||
|
||||
async def create_relations(self, relations: List[Relation]) -> Sequence[Relation]:
|
||||
"""Create multiple relations between entities."""
|
||||
|
||||
Reference in New Issue
Block a user